Using ES6 with your AngularJS project

Earlier this month Glen Maddern (aka. The guy) posted an article, Javascript in 2015, with a YouTube video giving "A brief tour of JSPM, SystemJS & ES6 features".
If you haven't seen it already, go take a look.

In a short time he has a front-end application written in ES6 running, with modules being loaded and transpiled (via Traceur) by SystemJS. And at the end he creates a self-executing, minified bundle of that application code, including source maps.

These tools make it easy to start using ES6 now with your existing AngularJS applications - which will almost certainly be step 1 on the migration plan to Angular v2.0. Even without that, the goodies from ES6 are too good to pass up.

As a demonstration, we're going to take angular-seed, update it to ES6 using SystemJS, bundle the app for production with systemjs-builder, and configure Karma to run unit tests using the karma-systemjs plugin.

SystemJS Setup

OK - lets get started.
We're going to clone an angular-seed repo, switch to a new es6 branch, then install dependencies from npm and bower. The dependencies for angular-seed are a bit old, so we've updated them too:

git clone https://github.com/angular/angular-seed.git  
cd angular-seed  
git checkout -b es6  
npm install --save-dev systemjs-builder karma-systemjs karma#~0.12 karma-chrome-launcher karma-firefox-launcher karma-jasmine  
bower install -f -S angular#1.3.x angular-route#1.3.x angular-mocks#~1.3.x system.js  

Next thing we need is a config file for SystemJS: app/system.config.js. This is where we tell SystemJS how to find certain modules. In this case, we're going to map the module names 'angular' and 'angular-route' to their long paths:

// Configure module loader
System.config({  
  baseURL: '/app/',

  // Set paths for third-party libraries as modules
  paths: {
    'angular': 'bower_components/angular/angular.js',
    'angular-route': 'bower_components/angular-route/angular-route.js'
  }
});

Next we'll change app/index-async.html to use SystemJS rather than angular-loader:

<script src="bower_components/html5-boilerplate/js/vendor/modernizr-2.6.2.min.js"></script>

<script src="bower_components/traceur/traceur.js"></script>  
<script src="bower_components/es6-module-loader/dist/es6-module-loader.js"></script>  
<script src="bower_components/system.js/dist/system.js"></script>  
<script src="system.config.js"></script>  
<script>  
  System.import('app').then(function() {
    angular.bootstrap(document, ['myApp']);
  });
</script>

<title>My AngularJS App</title>  

Last thing to do is add import statements to app/app.js to include the rest of the application, as it's what will be loaded by System.import('app'):

'use strict';  
import 'angular';  
import 'angular-route';  
import './components/version/version';  
import './components/version/interpolate-filter';  
import './components/version/version-directive';  
import './view1/view1';  
import './view2/view2';

// Declare app level module which depends on views, and components
angular.module('myApp', [  

Now if you load up app/index-async.html using a local webserver (The one included with angular-seed start with npm start) you should see the angular-seed application running as normal, only it's been loaded as a set of ES6 files.

This is just the bare minimum required to get existing code to ES6 using SystemJS.
You can add more ES6 syntax and rearrange the modules as you like.

Here's my suggestions:

  • Use classes for Controllers (Controller as), Services (.service()), and Providers
  • Use ES6 modules over angular modules
  • Use arrow functions - cause they're sugary sweet!

Bundling for Production

With our SystemJS config file already in place, we only need to pass that into systemjs-builder with a slight tweak to bundle our code for production. This is a simple node script you can run with node bundle.js that will bundle all the modules imported by app/app.js and output the result to app/bundle.js:

var builder = require('systemjs-builder'),  
 path = require('path');

// load SystemJS config from file
builder.loadConfig('./app/system.config.js')  
 .then(function() {
  // Change baseURL to match the file system
  builder.config({ baseURL: path.resolve('./app') });

  // Build a self-executing bundle (ie. Has SystemJS built in and auto-imports the 'app' module)
  return builder.buildSFX('app', 'app/bundle.js', { minify: true, sourceMaps: true});
 }).catch(function(err) {
  console.error(err);
 });

And finally we change app/index.html to include traceur-runtime.js and the bundle.js file we just created:

<script src="bower_components/traceur-runtime/traceur-runtime.min.js"></script>  
<script src="bundle.js"></script>  

Unit Testing

Karma works like a typical browser application: Load all