A day away from the SPA

The main motivation for this post came from using Aurelia, a Single Page Application (SPA) framework that uses SystemJs as a module loader in combination with BabelJs as a transpiler, enabling you to work with ECMAScript 2015 today without worrying about browser support (or lack thereof, rather).

But of course, no matter how hip it's become, not every web application has to be a SPA (even though SPAs can have more than one page).
So at CodeNamed we wondered, how would all this work on a "regular", multi-paged MVC 6 application? And down the rabbit hole we went. This post is about us seeing the light at the end of said hole and wanting to document the steps in what has, at times, become a tortuous path so that you, dear reader, can walk in and out of it without a scratch. Until some of the packages we're going to use receive another update, that is. Welcome to the Web.

The setup

Let me start by saying that this post is not about how to use KnockoutJs, or how to write tests with Jasmine. It's neither about teaching you MVC 6 nor the new ES2015 syntax. Instead, this post is about getting all of these working together.

jspm is a package manager for the SystemJS universal module loader, built on top of the dynamic ES6 module loader

Within a Command Prompt, go to the root of the project, which is one level above the wwwroot folder, and install and initialize jspm as follows:

npm install jspm
jspm init

The init command will ask a series of questions. On most of those you can just press Enter to accept the default option. Just make sure that you set the baseUrl to ./wwwroot and that you choose babel as the transpiler.

Here we're telling SystemJS to load a module called main. SystemJs will look inside the wwwroot/src folder (as defined in config.js) for a file called main.js.

2. main.js

main.js won't do much other than importing some of the modules that will be used throughout the application, like KnockoutJs or jQuery, so that we don't have to import them over and over in every module. Keep it DRY ;)

From this point on, the rest of the modules will have a central point to define the ko and $ variables.

The idea is that every MVC View gets a module (a JavaScript file) with the same name (although it doesn't matter; it's just for clarity). These modules will be extremely simple and will have only three tasks:

Import the Main module.

Import the KnockoutJs viewmodel for the current view, along with any other necessary modules (like, say, utility classes).

Call ko.applyBindings to get the view up and running.

3. Index.cshtml

The first thing that Index.cshtml needs to do, then, is import the module that will apply the KnockoutJs bindings (which we'll create in a minute), and of course, define some html.
To keep things simple, the view will only display a Name and Surname inputs and will show the concatenated full name below. Here's the view in its entirety:

As you can see, I have tried to apply some Bootstrap 3 classes and I've added Knockout bindings to the fields, but that's where the excitement ends.

4. fullName.js

Next up, we'll create the viewmodel that binds to the Index.cshtml view. I've placed it in the viewmodels folder I created earlier within src. This viewmodel is as simple as the view it binds to: it just defines the three properties that are needed in the view, and it all happens in the class's constructor.

You might be wondering what the point is of this "extra" file. Why not call ko.applyBindings(...) in the viewmodel itself? Simple: because of unit testing. Having the call to applyBindings on a separate file with virtually no code means that

We won't need to test that file, and

We won't need to worry about mocking the ko object on every spec.

In my first attempt I was actually calling ko.applybindings inside the viewmodel itself, but the problem was that, because I'm importing KnockoutJs in the main module and not in fullName where it is actually used, the test runner kept throwing a "ko is not defined" exception; and importing KnockoutJs into the test file didn't help.
As it happens, I'm happier with this setup. It might seem overkill for a small demo application such as this one, but not having to import KnockoutJs in every file on a large application, where you will need it on pretty much every module, is actually quite a welcome idea.

Start me up

At this point, we have a functional application. Go ahead, run it; bask in the wonder of your creation.
But, I hear you say, how can we guarantee our code is stable when we haven't written any tests?

Prepping to test

Earlier we installed the jQuery and KnockoutJS packages. Next up are Jasmine and Karma.

Jasmine is a behavior-driven development framework for testing JavaScript code

and Karma is a fantastic test-runner that will watch your JavaScript files for any changes and run the tests automatically, much like NCrunch does for .NET unit tests.

This time we'll install all the necessary packages from the npm repository so that we can benefit from VS2015's great npm built-in support.

Notice the --save-dev flag at the end of both commands. This tells npm that we'll only need these packages at development time.

Version mismatch

You might have noticed that we are installing the karma-babel-preprocessor package using a specific version.
That's because, at the time of this writing, Babel 6 is already out, but the current jspm (0.16.19) and SystemJs (0.19.9) versions don't support it yet. The latest version of karma-babel-preprocessor is already based on Babel 6. Version 5.2.2 is the latest version currently supported by jspm.

Babel 6 support is planned for SystemJS 0.20.0, which should be released soon.

We still need to configure Karma, but let's write a unit-test in Jasmine before we go any further.

fullName.spec.js

Inside the test folder, create the file that will hold our test. I called it fullName.spec.js, but it really doesn't matter.
The test itself will be as simple as our viewmodel, of course, but remember: the idea here is to get Jasmine and Karma up and running, not to explore the deep intricacies of JavaScript unit testing. My test file looks as follows:

Configuring Karma

Karma requires a quite a bit of configuration. Thankfully, a big chunk of the work will be done for us just by running a command and answering a couple of questions.
Again, then, within a Command Prompt, go to the root of the project (one level above the wwwroot folder), and run:

karma init

As I said, this will present you with a set of questions. Here's how to answer (by basically accepting every default answer):

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
>
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes

This will create a karma.conf.js file in the folder where you run the command. Now we'll have to edit this config file to tell Karma we're using a transpiler (BabelJs).

jspm

Open karma.conf.js, locate the line where the framweorks are defined and add jspm to the array.

frameworks: ["jspm", "jasmine"],

Next, we weed to configure jspm. Add the following elements anywhere in the config file (I added it just below frameworks):

The "jspm" section basically tells Karma where the jspm packages and our own javascript files are; the "proxies" section maps the packages path so that Karma can understand where wwwroot stands in relation to it's baseUrl. Remember that we removed the baseUrl setting from config.js? That would confuse Karma if left there. Adding the proxies completes the mapping.

Needles to say, adapt the loadFiles section to your needs in case you haven't followed my same folder structure.

Babel preprocessor setup

Ok people, hold on tight because this is the last step! If we tried to run the tests with Karma as it stands, Karma wouldn't be able to interpret the ES2015 code. Much like the application itself, Karma needs a transpiler.

In the preprocessors section we're just telling Karma to use BabelJs to pre-process both our sources and the unit tests. In the babelPreprocessor section we can configure Babel itself. We could, for instance, use optional ES2016 features like class decorators.

Time to se what the fuss is all about

If you haven't used Karma or any other JavaScript test runner before you might be wondering why on earth you'd want to go through all this trouble to set it up. Then again, if you're using NCrunch with Visual Studio you probably already know what to expect and know that it's brilliant.

Once more, open a Command Prompt and go to the root of the project, the folder where karma.conf.js lies and run the following command:

karma start

Karma will open a broswer window (a Chrome instance), but you can minify and ignore it for now. It becomes useful in case you want to debug some failing tests.

Other than dat, on the console you will see that Karma has run 2 tests, and that one of them is failing. I did that on purpose, remember?

Karma magic

Open fullName.spec.js and change this line expect(sut.fullName()).toEqual(""); into:

expect(sut.fullName()).toEqual("Sergi Papaseit");

And save the file while keeping an eye on the console... BAM! Karma has detected changes in one of the files it's been watching and has automatically run the tests again.
That means not having to take any extra steps to run your tests while you develop. Karma for the front end and NCrunch for the back-end and you'll know immediately if your code breaks any of the existing tests. If that doesn't put a smile in your face I don't know what will.

Can I use this in the wild?

You definitely can, but what I've shown here is not how we use it in a production environment. The setup of this blog actually requires SystemJs to transpile with Babel on the fly, in the browser. As you can imagine, this imposes quite a speed penalty on the application.

So how doe we use it? Without going into too much detail, because that is a whole post in and of itself, here's our setup: