Aurelia and Visual Studio 2015

If you have been following me for the last few weeks then you know I’ve been playing with ECMAScript 6, Web Components and Polymer quite extensively. This is all leading towards a major single page application that I want to be driven by the latest standards. My last step is in integrating a web framework. There are lots to choose from, but not many that take web components and ECMAScript 6 seriously. The one I’ve decided to play with is Aurelia as it meets all these requirements plus it has support in Web Essentials 2015.

Before I get into Aurelia for my own project, I want to go through the tutorial. However, I want to go through it with Visual Studio as my IDE (since I’ve already explored the non-IDE route and it is painful). The problem is that Aurelia sort of assumes a Node stack and I want this to run with a Visual Studio ASP.NET stack.

Integrating JSPM

My first problem is that Aurelia likes jspm. This makes sense – it’s built on top of the system.js loader and es6-module-loader, so it makes for a cleaner ES6 integration. But Visual Studio doesn’t natively support JSPM or ECMAScript 6. However, we can integrate it with some up-front work. First, create the Visual Studio project (or download mine at my GitHub repository).

Then open up a PowerShell prompt (you don’t have to be Administrator) and type in the following:

npm install -g jspm

This will install jspm and all its dependencies in your account. The jspm binary is placed in ~\AppData\Roaming\npm. I highly recommend you add this to your PATH permanently since all the global npm-installed tools get a link there. I can now create a jspm configuration for this project. To do this, change directory to the project and run the following (screen shot with prompts filled in):

blog-code-31-1

As you can see, I’ve selected the wwwroot directory and the babel transpiler. I did have to change around the back-slashes for forward slashes afterwards. Here is a look at the resulting package.json:

{
  "version": "1.0.0",
  "name": "AureliaTutorial",
  "private": true,
  "devDependencies": {
    "gulp": "^3.8"
  },
  "jspm": {
    "directories": {
      "baseURL": "wwwroot",
      "lib": "wwwroot",
      "packages": "wwwroot/jspm_packages"
    }
  }
}

In addition, a wwwroot/config.js was created – here are the contents of that:

System.config({
  "baseURL": "/",
  "transpiler": "babel",
  "paths": {
    "*": "*.js"
  }
});

Notice all the files under jspm_packages as well? We don’t need those to be checked in, so make sure you add jspm_packages to the list of ignores in .gitignore. I’ve done this and checked it in to my repository.

Starting the Tutorial

The first step in the tutorial was to create a HTML page called index.html. In the tutorial, this is created in the root directory. In my version, this is created in the wwwroot directory. It’s contents are also slightly different because I want to see what I can alter. Here is my version:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="jspm_packages/github/twbs/bootstrap@3.3.2/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="jspm_packages/npm/font-awesome@4.3.0/css/font-awesome.min.css">
    <link rel="stylesheet" type="text/css" href="styles/styles.css">
</head>
<body aurelia-app>
    <script src="jspm_packages/system.js"></script>
    <script src="config.js"></script>
    <script>
      System.import('aurelia-bootstrapper');
    </script>
</body>
</html>

I don’t actually have bootstrap, font-awesome or aurelia-bootstrapper yet. I do have system.js – this was included as part of the jspm init. In addition I have a starting point for config.js. My next step was to expand on the config.js based on what was in the skeleton config.js:

System.config({
    "paths": {
        "*": "*.js",
        "github:*": "/jspm_packages/github/*.js",
        "npm:*": "/jspm_packages/npm/*.js"
    }
});

System.config({
    "map": {
        "aurelia-bootstrapper": "github:aurelia/bootstrapper@0.9.5",
        "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.4.5",
        "aurelia-framework": "github:aurelia/framework@0.8.8",
        "aurelia-http-client": "github:aurelia/http-client@0.5.5",
        "aurelia-router": "github:aurelia/router@0.5.8",
        "bootstrap": "github:twbs/bootstrap@3.3.2",
        "font-awesome": "npm:font-awesome@4.3.0",
        "github:aurelia/binding@0.3.7": {
            "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.4.5",
            "aurelia-metadata": "github:aurelia/metadata@0.3.3",
            "aurelia-task-queue": "github:aurelia/task-queue@0.2.5"
        },
        "github:aurelia/bootstrapper@0.9.5": {
            "aurelia-event-aggregator": "github:aurelia/event-aggregator@0.2.4",
            "aurelia-framework": "github:aurelia/framework@0.8.8",
            "aurelia-history": "github:aurelia/history@0.2.4",
            "aurelia-history-browser": "github:aurelia/history-browser@0.2.5",
            "aurelia-loader-default": "github:aurelia/loader-default@0.4.3",
            "aurelia-logging-console": "github:aurelia/logging-console@0.2.4",
            "aurelia-router": "github:aurelia/router@0.5.8",
            "aurelia-templating": "github:aurelia/templating@0.8.14",
            "aurelia-templating-binding": "github:aurelia/templating-binding@0.8.7",
            "aurelia-templating-resources": "github:aurelia/templating-resources@0.8.10",
            "aurelia-templating-router": "github:aurelia/templating-router@0.9.4"
        },
        "github:aurelia/dependency-injection@0.4.5": {
            "aurelia-metadata": "github:aurelia/metadata@0.3.3",
            "core-js": "npm:core-js@0.4.10"
        },
        "github:aurelia/framework@0.8.8": {
            "aurelia-binding": "github:aurelia/binding@0.3.7",
            "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.4.5",
            "aurelia-loader": "github:aurelia/loader@0.3.5",
            "aurelia-logging": "github:aurelia/logging@0.2.5",
            "aurelia-metadata": "github:aurelia/metadata@0.3.3",
            "aurelia-task-queue": "github:aurelia/task-queue@0.2.5",
            "aurelia-templating": "github:aurelia/templating@0.8.14"
        },
        "github:aurelia/history-browser@0.2.5": {
            "aurelia-history": "github:aurelia/history@0.2.4",
            "core-js": "npm:core-js@0.4.10"
        },
        "github:aurelia/http-client@0.5.5": {
            "aurelia-path": "github:aurelia/path@0.4.5",
            "core-js": "npm:core-js@0.4.10"
        },
        "github:aurelia/loader-default@0.4.3": {
            "aurelia-loader": "github:aurelia/loader@0.3.5",
            "aurelia-metadata": "github:aurelia/metadata@0.3.3",
            "aurelia-path": "github:aurelia/path@0.4.5"
        },
        "github:aurelia/loader@0.3.5": {
            "aurelia-html-template-element": "github:aurelia/html-template-element@0.1.3",
            "core-js": "npm:core-js@0.4.10",
            "webcomponentsjs": "github:webcomponents/webcomponentsjs@0.5.5"
        },
        "github:aurelia/router@0.5.8": {
            "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.4.5",
            "aurelia-event-aggregator": "github:aurelia/event-aggregator@0.2.4",
            "aurelia-history": "github:aurelia/history@0.2.4",
            "aurelia-path": "github:aurelia/path@0.4.5",
            "aurelia-route-recognizer": "github:aurelia/route-recognizer@0.2.4",
            "core-js": "npm:core-js@0.4.10"
        },
        "github:aurelia/templating-binding@0.8.7": {
            "aurelia-binding": "github:aurelia/binding@0.3.7",
            "aurelia-templating": "github:aurelia/templating@0.8.14"
        },
        "github:aurelia/templating-resources@0.8.10": {
            "aurelia-binding": "github:aurelia/binding@0.3.7",
            "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.4.5",
            "aurelia-logging": "github:aurelia/logging@0.2.5",
            "aurelia-templating": "github:aurelia/templating@0.8.14",
            "core-js": "npm:core-js@0.4.10"
        },
        "github:aurelia/templating-router@0.9.4": {
            "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.4.5",
            "aurelia-metadata": "github:aurelia/metadata@0.3.3",
            "aurelia-path": "github:aurelia/path@0.4.5",
            "aurelia-router": "github:aurelia/router@0.5.8",
            "aurelia-templating": "github:aurelia/templating@0.8.14"
        },
        "github:aurelia/templating@0.8.14": {
            "aurelia-binding": "github:aurelia/binding@0.3.7",
            "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.4.5",
            "aurelia-html-template-element": "github:aurelia/html-template-element@0.1.3",
            "aurelia-loader": "github:aurelia/loader@0.3.5",
            "aurelia-logging": "github:aurelia/logging@0.2.5",
            "aurelia-metadata": "github:aurelia/metadata@0.3.3",
            "aurelia-path": "github:aurelia/path@0.4.5",
            "aurelia-task-queue": "github:aurelia/task-queue@0.2.5",
            "core-js": "npm:core-js@0.4.10"
        }
    }
});

That’s a lot of mapping, but it’s boiler-plate going forward, which is why I’v collapsed it in the code-block above. Note that I have changed the paths section in the top config section from the skeleton-config to match my environment. I’ve also only included the libraries that I need in the map section. A lot of the jspm_modules were for gulp and the build system which I don’t need because I’m going to handle those separately. In addition I need to define some dependencies in my package.json file:

{
    "version": "1.0.0",
    "name": "AureliaTutorial",
    "private": true,
    "devDependencies": {
        "gulp": "^3.8"
    },
    "jspm": {
        "directories": {
            "baseURL": "wwwroot",
            "lib": "wwwroot",
            "packages": "wwwroot/jspm_packages"
        },
        "dependencies": {
            "aurelia-bootstrapper": "github:aurelia/bootstrapper@^0.9.5",
            "aurelia-dependency-injection": "github:aurelia/dependency-injection@^0.4.5",
            "aurelia-framework": "github:aurelia/framework@^0.8.8",
            "aurelia-http-client": "github:aurelia/http-client@^0.5.5",
            "aurelia-router": "github:aurelia/router@^0.5.8",
            "bootstrap": "github:twbs/bootstrap@^3.3.1",
            "font-awesome": "npm:font-awesome@^4.3.0"
        }
    }
}

As with the config.js, this is boilerplate that comes from the skeleton-navigation project. Go back to your PowerShell prompt, change directory to your project directory and run jspm install. jspm will install all the dependencies for you.

Some dependencies were not installed on the first time I ran jspm install because GitHub rate limits unauthenticated connections. The fix is to use an authenticated GitHub connection. Go back to your PowerShell prompt and run:

jspm endpoint config github

The command will prompt you for your GitHub credentials. Register an account if you don’t have one already. You can re-run the jspm install to install the rest of the dependencies. Note that running jspm install seems to sometimes affect the config.js file, specifically changing the paths element to be jspm_packages/ instead of /jspm_packages/ – this is a major pain for me, so I just look at it after I run jspm install to ensure it is doing the right thing.

Note how you can pick between github/bower and npm packages? That’s called the best of both worlds.

A final item before I make my first test run – copy the styles/styles.css from the skeleton-navigation project to wwwroot/styles/styles.css.

At this point you will be able to run the project and see the effects. You will get something like the following in the console window:

blog-code-31-2

I’m on track with the tutorial because that notes that you don’t have an app.js either!

Integrating Gulp Building

Continuing the tutorial, you are asked to create an app.js and an app.html. I’ve created a project directory (not in wwwroot – at the top level) called src In it I placed copies of the app.js and app.html files. I’m not going to bore you with the contents here. The point of this article is to provide the additional bits to enable you to run through the tutorial.

Keeping the files in src isn’t going to help much. I want to transpile them using the babel compiler into system.js compatible modules and place them in the wwwroot/app directory. I’m going to have to do some work on the gulp front for this. I already have a template Gulpfile.js so I just have to add some dependencies into my package.json file and then wire up a task to do this. Let’s take a look at the tasks first. I’ve added the following into my Gulpfile.js:

var gulp = require("gulp"),
    babel = require("gulp-babel"),
    sourcemaps = require("gulp-sourcemaps");

gulp.task('build:js', function () {
    return gulp.src("src/**/*.js")
        .pipe(sourcemaps.init())
        .pipe(babel({ modules: "system" }))
        .pipe(sourcemaps.write({ includeContent: false, sourceRoot: "/src/" }))
        .pipe(gulp.dest("wwwroot/"));
});

gulp.task('build:html', function () {
    return gulp.src("src/**/*.html")
        .pipe(gulp.dest("wwwroot/"));
});

gulp.task("_build", ["build:js", "build:html"]);

My package.json dependencies looks like this:

  "devDependencies": {
    "gulp": "^3.8",
    "gulp-babel": "^4.0",
    "gulp-sourcemaps": "^1.5"
  },

Note that I’m building directly into wwwroot. When the Aurelia system asks for app.html, it expects it to be in the same path as app.js. I am asking system.js to modify the relative URL of the file by specifying a baseUrl. So, for instance, system.js will load ./app.js (which is actually, say, wwwroot/dist/app.js) and then the ./app.html (which is wwwroot/app.html) because I didn’t modify those paths without a custom router in the ASP.NET side of things.

Running this version (which is tagged t-3 in my repository) finally gives us some output and it works per the tutorial.

Adding Navigation

At this point I wanted to start putting my views in a sub-directory. The key to this is the moduleId in the router. But I am getting ahead of myself. I copied the app.html and app.js to a sub-directory or src called views and renamed them to welcome.html and welcome.js. I then put the following in the app.js file:

import {Router} from 'aurelia-router';

export class App {
    static inject() { return [ Router ]; }

    constructor(router) {
        this.router = router;
        this.router.configure(config => {
            config.title = 'Aurelia';
            config.map([
                { route: ['', 'welcome'], moduleId: 'views/welcome', nav: true, title: 'Welcome' }
            ]);
        });
    }
}

Note the change to the moduleId – I’ve now got my views in a sub-directory, just the way I like it. The app.html file was a direct copy of the one from the tutorial.

The tutorial then goes on to add a second page. I again put the files in the views directory before adding the second page to the config.map and altering it’s moduleId. A quick build and it was working again.

Note that I am not using browser-sync as yet. My preference is to do the build, then refresh after I see things are in the right place. I’ve found that the gulp watch process is fairly fragile under Visual Studio 2015 CTP 6 – hopefully, they will get this working better under the RTM.

I’ve checked in this checkpoint as AureliaTutorial-t-4 on my GitHub Repository.

Custom Elements

To keep my custom elements separate from my views, I placed the nav-bar into a new directory called elements (in the src tree). The app.html file became:

<template>
    <import from='./elements/nav-bar'></import>

    <nav-bar router.bind="router"></nav-bar>

    <div class="page-host">
        <router-view></router-view>
    </div>
</template>

The only real difference here is one of choice. I want my views and elements separated. Continue on to the child router (don’t forget to prefix your moduleId with views/).

Tutorial Complete. You can find the code to my version of the tutorial (including the steps along the way) on my GitHub Repository.