ES6 Module Transpiler - Tomorrow's JavaScript module syntax today

February 15, 2013

Modern JavaScript has a number of different ways to modularize your code. Each approach has its own advantages and disadvantages, but none of them strike the right balance. At Square we have a number of different web applications using a variety of different module styles. The ES6 module draft aims to find that balance, but it's not something browsers support yet. To let us use ES6's import and export syntax with today's browsers, we wrote the ES6 Module Transpiler library.

Today's module systems

CommonJS

Node.js and others use CommonJS, which provides a nice, easy to understand way of exporting and importing libraries that nicely avoids global namespace collisions:

// point.js
function Point(x, y) {
    this.x = x;
    this.y = y;
}
module.exports = Point;

// myapp.js
var Point = require("point");
var origin = new Point(0, 0);
console.log(origin);

AMD

Another approach is to use AMD (Asynchronous Module Definition), which allows you to break up your application into multiple files which can be loaded on demand. This approach keeps the namespacing advantage of CommonJS but adds the ability to partition your application:

// point.js
define("point", function() {
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }
    return Point;
});

// myapp.js
define("myapp", ["point"], function(Point) {
    var origin = new Point(0, 0);
    console.log(origin);
});

This advantage of AMD over CommonJS comes at a price: the syntax is cumbersome and not at all approachable for someone new to AMD, and it forces you to right-indent all your "real" code.

Tomorrow's module system

ES6 Modules

The ES6 specification is an evolving draft outlining the changes and features in the next version of JavaScript. At some point it will be finalized, but before that happens browser support for anything introduced in ES6 will be experimental at best and cannot be used for widely-deployed web applications. One of the cool features of ES6 is the module syntax. We can write our code above like so:

// point.js
function Point(x, y) {
    this.x = x;
    this.y = y;
}
export = Point;

// myapp.js
import "point" as Point;
var origin = new Point(0, 0);
console.log(origin);

ES6 module syntax has the conciseness of CommonJS but the flexibility to be implemented either synchronously or asynchronously.

ES6 Module Transpiler

Translating to the appropriate style is what ES6 Module Transpiler is designed to do. We can write our JavaScript using the new syntax and use the library's included compile-modules script to convert it to our desired format:

$ compile-modules -s -m point --type amd < point.js
define("point",
  [],
  function() {
    "use strict";
    function Point(x, y) {
      this.x = x;
      this.y = y;
    }

    return Point;
  });

$ compile-modules -s --type cjs < point.js
"use strict";
function Point(x, y) {
  this.x = x;
  this.y = y;
}

module.exports = Point;

Now you can write JavaScript using this shiny new syntax but still deploy to existing JavaScript runtimes in the real world. In addition to supporting CommonJS and AMD, you can still use the old school approach and stuff everything into the global namespace:

$ compile-modules -s --type globals < point.js
(function(exports) {
  "use strict";
  function Point(x, y) {
    this.x = x;
    this.y = y;
  }

  exports.Point = Point;
})(window);

There is one more thing. It works with CoffeeScript, too:

$ compile-modules -s --coffee -m point --type amd <<EOF
class Point
  constructor: (@x, @y) ->
export = Point
EOF
define("point",
  [],
  ->
    "use strict"
    class Point
      constructor: (@x, @y) ->

    return Point
  )

This library will be updated over time as the working draft of ES6 changes, so check out the current README for the correct syntax and a full rundown on how to use it.

Open Source

ES6 Module Transpiler was born out of Yehuda Katz's js_module_transpiler, which is effectively the same library but written in Ruby. We decided to rewrite it in JavaScript since we wanted something that supported CoffeeScript and didn't require pulling in yet another language (Ruby) when you're already working with JavaScript. The project is Apache licensed and is on Github. An online demo is available on its Github Pages site.

You can install it using npm just for its command-line script as shown above:

$ sudo npm install -g es6-module-transpiler

Or use it as a library:

import Compiler from "es6-module-transpiler";
var es6Script = "import jQuery from 'jquery';";
var amdScript = new Compiler(es6Script, "script-name").toAMD();

If you're developing a Node.js module using the ES6 module syntax you can even use the included require support to require files written using the new syntax directly -- no transpiling required:

// point.js
function Point(x, y) {
  this.x = x;
  this.y = y;
}
export = Point;

// myapp.js
require("es6-module-transpiler/require_support").enable();
var Point = require("./point"); // directly require file with imports/exports

While this is convenient for development, you should transpile your library for distribution so as not to incur a performance penalty on every call to require.

Contributing

We hope you enjoy using ES6 Module Transpiler in your apps, and we hope you contribute back any improvements you make!

Brian Donovan
Web Engineer. @eventualbuddha.

Comments

Get support help at squareup.com/support. We'll delete off-topic comments.