Fairwell dependancy Hell! Traceur gives you ES6 modules in the browser today.

ES6 modules, Yey!

I’ve been drilling down into the ES6 spec in more detail since last week’s posting to see what juicy pieces of JavaScript goodness I can leverage to improve my next AngularJS project. The feature that jumped out at me instantly is ES6 modules. If you’ve never been down the ‘JavaScript asynchronous script loading’ rabbit hole and you have a spare rainy afternoon to punish your brain, this article is a good starting point: http://www.html5rocks.com/en/tutorials/speed/script-loading/

Apart from JavaScript dependencies being an absolute hell hole that I’ve never encountered in any other language, the fact that it’s near impossible for even the best IDE’s to determine the dependencies for a JavaScript file has been bugging me for ages. I’m a big fan of Webstorm, but even it has no choice to present the whole JavaScript universe in it’s Intellisense. That makes things a royal pain in the neck in terms of coding productivity. So, ES6 modules to the rescue! can we really say goodbye to our dependency troubles in the browser and IDE? From my current investigations things look really promising.

Transpiling is now a thing!

The main thrust of my last posting was that current browser support for ES6 is sketchy at best. For example, IE10 has next to no support for ES6. Microsoft have rightfully focused their efforts on the new Edge browser. However because Edge is only going to be available in Windows 10 for now, IE11 and it’s descendants are going to be with us for a few years yet. So if you want to use ES6 features in today’s browser landscape, transpiling and browser shims are the only options. Two of the most promising technologies are Babel and Google’s Traceur

With a skim through Traceur’s ‘Getting Started’ page it quickly became evident that it can do in-line transpiling. As I understand it, in-line transpiling is slightly different from your classic shim because it doesn’t modify and extend the core JavaScript namespace, rather it translates your ES6 code to code that will run on ES5. I might be wrong here and I might be in danger of splitting hairs, comments are welcome.

Anyhow, what this means is that you can jump into ES6 without going through the pain of modding your build processes and tool-chain (more on that later). So it’s a great low risk way to dip a toe in the water. Just include the 2 Traceur script files from Google’s CDN in your main HTML page and your up and running!

Leading by Example

Traceur uses the script type ‘module’ to differentiate ES6 code from normal Javascript. Because the browser doesn’t recognize this as a valid script type it will load it but not execute it. This will be done by Traceur once its bootstrapped.

Here’s my example of a simple HTML page that bootstraps Traceur which then transpiles and runs an ES6 script.

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>ES6 modules test with Traceur</title>

</head>
<body>
<h1 id="message1">Loading...</h1>

<h1 id="message2">Loading...</h1>

<h1 id="message3">Loading...</h1>

<!-- Include the Traceur scripts from the google CDN to shim/inline-transpile ES6 -->
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<!-- Traceur uses a special script type for ES6 code - so that the browser doesn't attempt to execute this straight off the bat -->
<script type="module" src="main.js"></script>

</body>
</html>

Pretty Simple eh? Just 3 script tags is all that’s needed for your SPA. The rest of the dependencies will be defined in your JavaScript code and won’t be polluting the global name-space – just like a proper language ;). Now lets look at using ES6 modules with this set up…

Modularity got us to the Moon, but can it tame JavaScript?

So before we look at main.js and see how it loads a module, lets take a look at a noddy little module that I cooked up:

export default function() {

    var element = document.querySelector('#message1');
    element.innerHTML = "hello from my testmodule's default";

};

export function greeter() {

    var element = document.querySelector('#message2');
    element.innerHTML = "hello from my testmodule";

};


export class Greeter {
    constructor(message) {
        this.message = message;
    }

    greet() {
        var element = document.querySelector('#message3');
        element.innerHTML = this.message;
    }
};

Note the use of the export keyword. I’m exporting three things here,:

  1. The default function for this module. This is useful for the popular pattern used frequently in node modules, to export a single entry point for the module
  2. A simple function called ‘greeter’
  3. An ES6 class definition. Just to keep you OO guys happy. I can hear groans from the audience 😉

This module is contained in a file called ‘modules/testmodule.js’ relative to the main HTML page. I’ve attached the working source code to the bottom of this post so you can have a play around with it.

Now for main.js:

/* module path is relative to the HTML file that included this script */
/* Note the current traceur release as of 29/06/2015 needs .js appended on to the module path.
 This isn't in line with the final ES6 draft spec which states that the .js should be omitted */

/* import the default export from the test module */
import defaultFn from "modules/testmodule.js";

/* import all module exports from the testmodule into the test module namespace */
import * as testmodule from "modules/testmodule.js";

/* alternately you can import individual exports into this file */
//import {greeter,Greeter} from "modules/testmodule.js"

/* Call the default export from my testmodule */
defaultFn();

/* Call a named function export from my test module */
testmodule.greeter();

/* Instantiate a class export from my test module */
var greeterClass = new testmodule.Greeter("Hello, from the Greeter Class!");
greeterClass.greet();

I think the in-line comments explain things pretty satisfactorily here so I won’t elaborate further as I want to talk about IDE and tooling before you switch off.

Lint us a hand here!

This modularity is all well and good but what are we breaking by using ES6? It turns out that the answer is not that much. Looking at the Chrome developer tools – the scripts are included in the debugging view albeit in their ES5 incarnations. Apparently if we actually do the transpiling in our build processes we get source maps to help between the ES6 -> ES5 translations – but that’s for another day.

I just love using Webstorm, for my day to day hackery and it does not disappoint when it comes to ES6. There’s a setting that allows one to specify the Javascript language version. This quickly solves most problems we’ve caused but if your a user of JSLint you need to switch over to ESLint which supports ES6 syntax and is also supported natively by Webstorm. I’ve included an .eslintrc file in the sample code to get you started. I’m sure users of Sublime text will have similar options available but I can’t be of any help here.

Conclusion

I’ve tested the example code with the current releases of Chrome, Firebox and IE11 and everything seems to run just fine. I would be interested to hear if anyone finds problems with the code, gets it running on other browser versions or any other interesting factoids.

What I would like to do, if time permits, is to get under the hood and see if Traceur is actually loading the scripts asynchronously and can deal with all that crazy complicated edge case stuff around temporal dead zones and the like. Whilst I have the hood up I’d like to profile Traceur running in-line and see if the performance is viable to use without bringing it in to the build process for small Phonegap mobile apps. I’ll post any interesting things I discover on this blog in the near future.

Download Example Code for using ES6 modules with Traceur