Deploying ECMAScript 6

[2015-04-08] esnext, dev, javascript
(Ad, please don’t block)

This blog post describes the options you have for deploying ECMAScript 6 in current JavaScript environments. It is selective w.r.t. the amount of tools it covers. If you want a comprehensive list of tools, I suggest you look at Addy Osmani’s “ECMAScript 6 Tools”.

Consult the blog post “Using ECMAScript 6 today” for an overview of ES6 features.

Using ECMAScript 6 today  

What options do you have for using ECMAScript 6 today?

ECMAScript 6 features are continually appearing in engines. You can look up which ones are already supported where in Kangax’ “ECMAScript 6 compatibility table”. I’d expect first JavaScript engines to fully support ES6 in late 2015 or early 2016. It will take longer until all current engines do so.

Especially if you take support for legacy engines into consideration, compiling ES6 to ES5 will be the only viable option for using ES6 for quite a while. Compiling from source code to source code is also called transpiling. You can transpile ES6 either before deployment (statically) or at runtime (dynamically). The next section explains how that works, later sections describe other ES6 tools and libraries.

The nice thing about ES6 is that it is a superset of ES5, which means that all of your ES5 code bases are already valid ES6. This helps tremendously with adopting ES6-specific features, because you can do so incrementally.

Using ECMAScript 6 natively  

As soon as the first engine fully supports ES6 and until all non-ES6 engines go away, a hybrid approach could be used for client-side apps:

  • The server has two versions of each file: the native ES6 version and its transpilation, an ES5 version.
  • When the web app starts, feature detection is used to check whether ES6 is fully supported. If it is, the ES6 version of the app is used. Otherwise, the ES5 version is used. How exactly this process is going to work is not clear yet, no best practices have been established, so far.

npm may eventually support two versions of the same module, which would enable you to deliver libraries as both ES5 and ES6 for Node.js, io.js and client-side module systems that are based on npm.

Transpilation tools  

There are three essential choices that you have to make for transpilation:

  • A transpiler (for your code)
  • A package manager (to install existing libraries)
  • A module system (for the complete app)

Note that the choices are not completely independent, not every module system works with every package manager etc. The next sections explain each of these choices in more detail.

Choosing a transpiler  

A transpiler compiles your ES6 code to ES5. Popular choices are:

  • TypeScript: Is basically ECMAScript 6 plus optional type annotations.
  • Traceur: is an ES6 transpiler by Google, the first popular one. Pronounced French, /tʁa.sœʁ/; an English approximation is “truh-SIR” (source, listen to native French speakers pronounce this word).
  • Babel: is a newer ES6 transpiler that whose popularity has grown tremendously recently. Babel supports React’s JSX syntax in addition to ES6. Pronounced “babble”.

You can transpile the code either:

  • Statically (before deployment)
  • Dynamically (at runtime)

Static transpilation  

As a build step, TypeScript, Traceur and Babel let you produce ES5 code in the following module formats. You can either invoke them directly or use a build tool (grunt, gulp, broccoli, etc.).

  • AMD
  • CommonJS
  • ES6 module loader API: The ES6 code is transpiled to ES5 code that uses this API via a polyfill. This format is not supported by TypeScript.

In browsers, such ES5 modules are loaded via one of the module systems described later. On Node.js, you can use the built-in module system (other options exist, e.g. webpack and the ES6 Module Loader Polyfill).

Dynamic transpilation  

In browsers, you transpile dynamically via a library plus a custom <script type="...">. This option exists for Traceur and Babel.

For Node.js, Babel has tools for on-the-fly compilation. These are described in a separate blog post.

Choosing a package manager  

You need a package manager for installing third-party libraries. These are three popular ones:

  • npm (CommonJS modules): is a package manager that was originally created for Node.js, but has grown in popularity for client-side development thanks to module packaging and loading tools such as browserify and webpack.
  • Bower (CommonJS or AMD modules): is a package manager for client-side code.
  • jspm: is a package manager for SystemJS (see next bullet list). It can install modules from a variety of sources, including GitHub and npm. One key feature of jspm is that external modules can also be written in ES6 (and will be transpiled), not just your own modules.

Choosing a module system  

Module systems bring support for modules to ES5 browsers (Node.js has a built-in module system). That way, you can build your app out of modules – your own and library modules. Popular module systems are:

  • RequireJS: is a loader for AMD modules, which can be statically created via TypeScript, Traceur or Babel. Loader plugins (based on Traceur and Babel) enable it to load ES6 modules.
  • Browserify: packages CommonJS modules (including ones installed via npm) so that they can be loaded in browsers. Supports ES6 modules via transforms (plugins) based on Traceur and Babel.
  • webpack: a packager and loader for either CommonJS modules (including ones installed via npm) or AMD modules (including ones installed via Bower). Supports ES6 modules via custom loaders (plugins) based on Traceur and Babel.
  • SystemJS: A module system based on the ES6 Module Loader Polyfill that supports ES6 modules and the ES5 module formats CommonJS, AMD and “ES6 module loader API”.

Example setups  

Separate blog posts describe three example setups:

Other useful ES6 tools and libraries  

  • Test tools (such as Jasmine and mocha) can mostly be used as is, because they work with the transpiled code and don’t have to understand the original ES6 code. Babel’s documention has information on how to use it with various test tools.

  • The following linters all support ES6, but to varying degrees:

    • JSLint (focus: enforcing coding practices)
    • JSHint (focus: enforcing coding practices)
    • ESLint (focus: letting people implement their own style rules)
    • JSCS (focus: enforcing code style)
  • Shims/polyfills enable you to use much of the ECMAScript 6 standard library in ES5 code:

  • ES6 parsers:

ES6 REPLs  

There are many REPLs (command lines) out there for interactively playing with ES6. The obvious choices are the interactive online playgrounds of the following projects:

Additionally, Babel brings ES6 support to the Node.js REPL via its babel-node tool.

What ES6 features can be transpiled?  

ECMAScript 6 has three kinds of features:

  • Better syntax for existing features
  • New functionality in the standard library
  • Completely new features

Better syntax for existing features  

For example:

  • Classes
  • Modules

These can be relatively easily compiled to ES5. For example, this is an ES6 class:

class Point {
    constructor() {
        this.x = x;
        this.y = y;
    }
    toString() {
        return `(${this.x}, ${this.y})`;
    }
}

In loose mode, Babel produces nicer ES5 code, at the cost of not being completely faithful to ES6 semantics. This is the previous code, transpiled in loose mode:

"use strict";

var _classCallCheck = function (instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
};

var Point = (function () {
    function Point() {
        _classCallCheck(this, Point);

        this.x = x;
        this.y = y;
    }

    Point.prototype.toString = function toString() {
        return "(" + this.x + ", " + this.y + ")";
    };

    return Point;
})();

New functionality in the standard library  

For example:

  • New methods for strings, arrays
  • Promises
  • Maps, Sets

These can be provided via a library. Much of that functionality (such as String.prototype.repeat()) is even useful for ES5. A later section lists a few such libraries.

Completely new features  

These features can never be transpiled completely faithfully. But some of them have reasonable simulations, for example:

  • let and const: are transpiled to var plus renaming where necessary, to avoid name clashes. That is, immutability is usually not guaranteed.
  • Symbols: transpiled to objects with unique IDs that are used as property keys whenever the bracket operator coerces them to strings. Additionally, some property-enumerating functions (such as Object.keys()) have to be patched to ignore property keys coming from symbols.
  • Generators: are compiled to state machines, which is a complex transformation.
  • WeakMaps and WeakSets (keys are stored in values, which works, because WeakMaps and WeakSets don’t allow you to clear them or to iterate over their entries).

Others are impossible to transpile (in a straightforward manner):

  • Proxies
  • Subclassable built-in constructors (e.g. Error and Array)