Porting your Javascript to ES6

Will Becker
Lexical Labs Engineering
10 min readJul 30, 2015

Have you heard? We aren’t beholden to old versions of Internet Explorer any more! And though we don’t have to expect everyone to have version 72 of your favourite browser yet, we can still program as if they do thanks to the kind folks at Babel.

But if you have a project that was built never thinking that Javascript would change, how do you go about updating it?

We’ve just migrated our codebase that lives

  • on the backend in Node with Mocha tests (& Istanbul) and
  • on the frontend in Backbone, React (using JSX), RequireJS with Karma tests (& Istanbul again)

to the future and have put together a checklist of things that you should look out for when upgrading. This won’t be another how to use ES6 post, but will focus on the pain points you will probably go through in using it for the first time.

One big theme that comes out of it however is that changing your code is only half the struggle. The other half is making your hypothetically working code into something you can practically deploy!

1. Getting it running.

The backend (like everything you’ll find in this post) is a lot easier than sorting out the frontend.

To migrate node over, all you need to do is use babel’s CLI instead of node is to npm install babel and use it instead of node:

./node_modules/.bin/babel-node -- server debug

Importantly note the double dash — it tells babel not to steal arguments from your app, without it, it decides you are passing the debug command to babel.

Your frontend code may be trickier. One thing you need to manage yourself is the ES5/ES6 divide. In theory, you should be using r.js or webpack to bundle all this up for you, but you will probably want an unbundled version so you can make hot changes on the fly and see them in your browser.

Since we use RequireJS, you can hook up requirejs-babel, which allows you to prepend an include with `es6!` and it will babelify it for you.

This can be a drag, so you can actually set up your require.config() so that you can say that anything that matches a regex should be ES6.

require.config({
//...your normal config here...
config: {
es6: {
resolveModuleSource: function (source) {
if (/your_es6_source/.test(source) {
return "es6!" + source;
}
}
}
}
})

Note that this code is in ES5, because only the things that get required from here on in are babelified!

2. Modules

AMD and CommonJS have been great friends over the last few years. Proper module management is something that we’ve had crying out for and it’s great that it’s something that we can now take for granted.

The new ES6 syntax is great, but easier to convert to from CommonJS:

var module1 = require("module2");
var module2 = require("module2");
modules.exports = function () {}

which transforms to:

import module1 from "module1";
import module2 from "module2";
export default function () {}

You could write a vim macro or use Sublime’s multiline editing to get this working quite quickly. Not so much AMD because the location and variable name are split up:

require(["module2", "module2"], function (module1, module2) {
return function () {}
});

Luckily, with AMD, tools exist to help you here.

3. Testing + Code Coverage

Our test process has 3 main phases two it:

  • Static code tests (linting) — did I screw something obvious up?
  • Unit/integration tests — did I screw something non-obvious up?
  • Code coverage — am I being lazy and writing code that will bite future me ?

All of these are affected by your transition to ES6.

Linting

Use eslint! It’s so sexy in so many ways, not least because you just specify an option in your .eslintrc file and you are now linting in ES6:

{
"env": {
"es6": true
}
}

Yay.

Tests

Tests are just as easy to get working.

HAHA!

In reality testing ES6 sometimes feels like building a hack on top of a cludge. I’m sure it will get better. But this is where most of my pain was!

Again, the backend isn’t so bad: Just go into your mocha config, and ensure that it is has something like this somewhere:

require: "babel/register"

Or if running from a command line like I normally have running under a watch flag:

node_modules/mocha/bin/mocha test/node/**/*.js --watch --compilers js:babel/register

So on the other hand, sorting the frontend on top of karma out is the kind of job you’d give the new person to do just to make them empathise with all the pain that you’ve gone through before ES6 was but a dream in Brandon Eich’s dreamy mind.

In retrospect, I guess it’s not so hard. It’s just the voyage of discovery was akin to Odysseus’. The process involves, installing babel-karma-preprocessor which does just what you think it should do. Then add a preprocessor to your karma config that processes all your code (ie: don’t run it on libraries!)

{
preprocessors: {
"where_your_src_lives/**/*.js": ["babel", "coverage"],
"where_your_tests_abide/**/*.js": ["babel"]
}

Ignore the coverage bit for now. Just know that you will need to handle your source and test files differently.

Babel will then output all your files kindly as CommonJS modules that you browser may well kick up a fuss at, since you are running RequireJS (aren’t you?). So instead, you need to either follow the advice here and use a loader, like browserify, and then find that it doesn’t work, nor does SystemJS, nor does anything else. Or you can do is simple and use the power of the preprocessor’s options (in your karma config):

babelPreprocessor: {
options: {
modules: "amd"
}
}

Note you can use a bunch of things there, run babel -h to find out more.

Code coverage

Last thing you need to get this creaking pile of potential beauty flowing in a way that will make you feel good about yourself is istanbul. I tried out all the others. This is the best.

As you probably know, code coverage tools pre-process your code and add extra functions before every statement and expression so that it tricks the Javascript engine you are running into telling it what it run. It’s quite smart and quite stupid at the same time. But doing this on top of babel, which is also a preprocessor makes thing more complicated. You are in effect going from ES6 => ES5 => Instrumented ES5.

To be honest, when I run the backend mocha tests, I don’t even run mocha, I just run mocha-istanbul which seems to just work. I haven’t ‘fessed up that I still use grunt and grunt-mocha-istanbul just does what it says on the tin. Use it!

For karma you will want to use isparta. It fits into the process fairly well — you just need to update a couple things that used to say istanbul to isparta and it works. You don’t need to use a special build of istanbul that handles React anymore either, since babel reads JSX out of the box.

4. Modernising your code

Once you’ve got through this head/heartache, you can get to the fun bit — making everything flashy and new!

There is a checklist of things that I look to improve in every file I touch now:

Eliminate bind().

You are probably using bind() either explicitly or implicitly in one of three ways, for example, given this code (that doesn’t work):

function Adder(amount){ this.amount = amount };
Adder.prototype.addLater = function (x) {
setTimeout(function () {
console.log(x + this.amount);
}, 1000);
};
new Adder(2).addLater(1);

you would make it work in ES5 by:

setTimeout(function () {
console.log(x + this.amount);
}
.bind(this), 1000);

Or if you had to support IE8, using ES4 style:

Adder.prototype.addLater = function (x) { 
setTimeout(
function (fn, that) {
return function () {
fn.apply(that, arguments);
};
}(

function () {
console.log(x + this.amount);
},
this
),
1000
);

};

Which was more likely done using a library…

setTimeout(_.bind(function () {
console.log(x + this.amount);
},
this), 1000);

At any rate… all this sucks. Now you can write this as a one liner:

function Adder(amount){ this.amount = amount };
Adder.prototype.addLater = function (x) {
setTimeout(() => console.log(x + this.amount), 1000);
};
new Adder(2).addLater(1);

You’ll find that you use this all the time when doing async programming. Which you will being doing a lot of.

There is an inestimable joy in finding that I was doing some super deeply nested stuff, that magicked into a one liner. For example, to run a bunch of database calls one after the other, you may use the following:

sequence(_.map(ids, function (id) {
return _.bind(function () {
return this.fetchDataAboutThisId(id);
}, this);
}, this))

This turns into:

sequence(ids.map((id) => () => this.fetchDataAboutThisId(id)))

Hooray for implicit returns! This is so much more readable, which hopefully means you will understand your code better and make fewer mistakes.

I can’t overstate how much I love the idiom

(arg) => () => this.doSomething(arg)

It says:

  • take a function with a parameter, (first parenthesis)
  • and give me a function that uses that argument.

I find I use it many times when ordering async calls. So elegant and graceful compared to the ungodly cluster of functions and binds that blighted my IDE. It must almost be as nice as writing Lisp.

No more function () {}s

So even if you get rid of the functions that are bound, there’s a bunch of other times when you just use functions. Don’t.

Two cases I’ve been using them, is creating class-y code. You don’t need to access prototype anymore however! Just use classes.

The one downside to this, however, is multiple inheritance.

You might be using this, for example to mixin Event Emitters into you code, as well as inheriting from something else.

In this case, you can use the following:

export default function mixin(...mixins) {
const clazz = (class Target{});
const target = clazz.prototype;
mixins.forEach((source) => {
source = source.prototype;
Object.getOwnPropertyNames(source).forEach((name) => {
if (name !== "constructor") {
Object.defineProperty(target, name, Object.getOwnPropertyDescriptor(source, name));
}
});
});
return clazz;
}

and then go:

class YourMomma extends mixin(Girth, Kvetching, Cake) {
eatPie (amount) {}
}

The good thing about the above approach is that it doesn’t extend the first mixin’s prototype with the things from the other ones’. And that it works without Babel throwing heinously unhelpful error messages.

The other case, is when you need to use the arguments object (you can’t use arguments with ()=>{}) But you don’t need to do that anymore! Instead of:

function () {
whatAmIDoing.apply(this, arguments);
}

You can go:

(...args) => whatAmIDoing(...args);

So, you pretty much never need to use apply() either any more.

No more var

Treat var as a code smell. Use const or if you really really have to use let. But try not to use let. Keep your code functional. Or use functions anyway:

Bad:

let x;
if (something) {
x = someValue();
}
while (somethingIsTrue) {
keepLooking();
}
x = someOtherValue();
f(x);
g(x);

Better:

const x = (() => {
if (something) {
return someValue();
}
while (somethingIsTrue) {
keepLooking();
}
return someOtherValue();
})()
f(x);
g(x);

In this highly contrived example, you can see that we can give a value to x as soon as we define it. This is so good, because like putting final through a bunch of procedural code in, say, Java, it means that you have less things mutating, you write your code in cleaner, more testable ways and you have one less vector to shoot yourself in the foot.

If but I could const an arg. Or a class variable. Or make a class variable private. But I can dream.

Destructuring

This is pretty neat for express — you have a route like:

/api/v1/user/:user_id/project/:project_id/action/:action_id

You can then write a handler for it like:

(req) => {
const {user_id, project_id, action_id} = req.params;
...
}

Which is lovely!

You can even change the names to camel case if you so prefer:

const {
user_id: userId,
project_id: projectId,
action_id: actionId
} = req.params;

Haven’t the Javascript gods thought of everything?

Promises

This is the one thing I don’t really dig yet about ES6. Oh, I’m sure they are perfectly fine promises that babel would never really have the heart to break. But, I want more from a promise than to just get called. I want to be able to manipulate them. I want when.js’ lovely library of primitives so I can run things in sequence and pipeline them and use logic like all() and what not. That doesn’t come out of box yet. Maybe it never will. But I still use underscore/lodash for all my iterating needs still, because I like to groupBy() and pick() occasionally. And when I do, I just like to use their mapper’s and forEacher’s, if only for old time’s sake. Maybe I’m a sentimental developer. Maybe it’s faster.

So we’re probably never going to migrate entirely to native promises. I’ll start using them just for a laugh though!

The end bit

Thanks for sticking around so long. I hope you are one of 3 other people there who share my stack and found this useful. I will put together a yeoman script that grabs you all this loveliness out of the box. I expect a Christmas Card in return.

I guess the question is, at the end of the day/post, is it all worth it?

I spent 4 days doing this, in which I could have spent writing more code in ES5. According to XKCD, if it saves me more than a minute, 5 times a day then yes. Since I’m programming non-stop these days trying to get Lexical Lab’s first product out, then I’m sure the new language features will make my debugging and ability to reason about my code about this kind of bump. I do dread worrying about whether I am just decreasing the complexity of my code, while increasing the complexity of the scaffholding that is holding it all together. It’s a fine line we tread.

Hopefully over the coming years this is not the bleeding edge any more and we can code concisely and beautifully with easy artistry. But I have the feeling that we’re just opening our world up to more things that will make us more productive, yet more brain-full. What we really need to go along with this are ways to reduce the cognitive load, so that we can just experience the joy of programming in our problem domain. But, hopefully this is a step in the right direction for Javascript developers to experience a little bit more of that joy!

--

--