Using ES6 with npm today

James Allardice on


On February 20th the first release candidate of the 6th edition of the ECMAScript specification (ECMAScript 2015 as it is now officially known) was released. The language is rapidly stabilising and the new features are slowly but surely making their way into the existing JavaScript engines. Unfortuantely, it will still be a while before we're able to write ES6 code and have it run natively in either browsers or Node and io.js. Thankfully TC39 (the group responsible for the development of the ECMAScript specification) put a lot of effort into ensuring backwards compatability so it is possible to use a large and growing proportion of ES6 today.

Transpilation

The ability to do so is all thanks to projects such as Babel (formerly known as 6to5) and Traceur. Commonly known as transpilers (although perhaps more accurately called source-to-source compilers), these are examples of software that can take ECMAScript 6 source code and output valid ES5 that will run in all modern, and even some not-so-modern, JavaScript environments.

At Mammal we use Babel, mainly because its level of support is way ahead of most of the alternatives but also because it has fantastic documentation and a great REPL on its website.

Getting started

When starting work on a new project with ECMAScript 6 we tend to set up a directory structure to keep the source separate from the compiled ES5. The source directory is usually src and the output directory is usually lib. Those are the names I'll use throughout the following examples. The src directory is kept under source control as usual and the lib directory is ignored (by adding it to .gitignore for example).

Installing Babel

If you don't already have Babel installed you can get it from npm:

npm install -g babel

With Babel installed you can easily compile your ES6 source files. Let's assume you have some in the src directory already. The following command will take any file with the .es6, .es or .js extension, compile it to valid ECMAScript 5 and place the output in the lib directory:

babel -d lib/ src/

If you want to automate the process there are Babel plugins for all the common JavaScript build tools, including Grunt, Gulp and Brocolli.

Polyfilling the standard library

Babel, being a source-to-source compiler, doesn't give us everything we need to take full advantage of the changes introduced to the standard library. That includes, for example, the new Map and Set constructors and a bunch of new Array methods. To use these in our code today we need a polyfill. There are numerous ES6 standard library polyfills but the one we use at Mammal is core- js, of which Node or io.js and browser builds are available.

Writing ES6

Now that we've covered the tooling required to build our ES6 projects let's put it all together. I'm not going to focus on any of the new language features here as that's a huge topic in its own right and beyond the scope of this article. If you need a reminder this feature list by Luke Hoban is a great place to start.

Let's create an ECMAScript 6 source file in the src directory. Let's call it person.es6. The main reason for the .es6 extension, rather than the perhaps- more-correct .es is that GitHub automatically detects it and applies the correct syntax highlighting:

import 'core-js/shim';

export default class Person {

  constructor( name ) {
    this.name = name;
  }

  sayHello() {
    return `Hello ${ this.name }!`;
  }

  sayHelloThreeTimes() {
    let hello = this.sayHello();
    return `${ hello } `.repeat(3);
  }
}

In this admittedly silly example we're using numerous new bits of syntax which will be handled by Babel, and a new library method, String#repeat, which will be handled by the core-js shim. To see how this script ends up once it's been compiled to ES5 you can run the Babel command given above or check it out in the REPL.

Publishing to npm

What we've covered so far is enough to be able to write, compile and run ES6 programs internally, but a little more is required if you want to publish your script to npm. It's not really feasible to simply publish your ES6 source and expect everyone else who uses your program to compile it themselves.

Fortunately, npm has the ability to modify your project before publishing via the prepublish script option, a feature that's already widely used for compiling CoffeeScript.

Let's add a package.json file to the root of our project, alongside the src and lib directories:

{
  "name": "person",
  "version": "0.1.0",
  "scripts": {
    "compile": "babel -d lib/ src/",
    "prepublish": "npm run compile"
  },
  "main": "lib/person.js",
  "dependencies": {
    "core-js": "^0.6.0"
  },
  "devDependencies": {
    "babel": "^4.6.0"
  }
}

Notice the compile script which is basically just the Babel command given previously. This allows you to run npm run compile without having to type out the directories every time. The prepublish script is automatically run whenever you type npm publish.

Also notice how Babel is listed under the development dependencies. This is so anyone who wants to contribute to the project doesn't have to have Babel installed globally on their system. npm adds executable files within a project's node_modules directory to your path when it runs commands so this makes things easier for other developers.

The .npmignore file

There is one final step necessary to make sure the compiled code ends up on npm rather than the source. The .npmignore file is used to exclude files from a package. If a .gitignore file is present in a project directory but an .npmignore file is not, npm will ignore anything matched by the .gitignore file. This is a problem in our case because we've told git to ignore our compiled lib directory. To solve this all we need to do is add an .npmignore file alongside the .gitignore file:

src/

With this file in place it's safe to run npm publish which will compile the ES6 source and push a package containing the result, but not the source, up to npm.

Summary

  • Write ES6 code and use a source-to-source compiler like Babel or Traceur to turn it into valid ES5
  • Use an ES6 standard-library polyfill such as core-js
  • Remember to add an .npmignore file if you're publishing a package to npm

You can see a complete example of this in our update-couch-designs repository. The project is a simple script that we use internally to update and create CouchDB design documents.

Join the discussion on Hacker News.