2015-04-05

Using transpiled ES6 on Node.js

This blog post is outdated (it covers Babel 5). Please read Sect. “Node.js setup: Statically transpiled ES6 via Babel” in “Setting up ES6”.


This blog post explains how to use ES6 on Node.js by transpiling it to ES5 via Babel.

A previous blog post showed how to dynamically transpile ES6 at runtime (also via Babel). That is more convenient and should work for most projects, but occasionally you may want a simpler and faster setup for your runtime environment.

Installation

Installation consists of downloading the repository node-es6-demo and executing the following commands, which install all npm packages that the repository depends on:

    $ cd node-es6-demo/
    $ npm install

The repo has the following structure:

    node-es6-demo/
        es5/
        es6/
            myapp.js
        gulpfile.js

Source maps

Source maps help whenever a language is compiled to JavaScript. Compiling source code to source code is also called transpiling. Examples of transpilation include:

  • Minification (normal JavaScript to minified JavaScript)
  • CoffeeScript
  • ECMAScript 6 (ES6 to ES5)

A source map is a file that accompanies the transpilation result and maps the lines of the result to lines in the transpiled files. This information can be used by error messages and debuggers to refer to lines in the original instead of the transpilation result. There are two ways to let tools know about a source map: Either the transpilation output refers to the source map file in its last line or it embeds the file’s contents in that line.

For more information on source maps consult the article “Introduction to JavaScript Source Maps” by Ryan Seddon on HTML5 Rocks.

The gulp file

I am handling transpilation via the build tool gulp. It is configured via a file gulpfile.js in a project’s directory. Ours looks as follows:

    var gulp = require('gulp');
    var sourcemaps = require('gulp-sourcemaps');
    var babel = require('gulp-babel');
    
    var path = require('path');
    
    var paths = {
        es6: ['es6/**/*.js'],
        es5: 'es5',
        // Must be absolute or relative to source map
        sourceRoot: path.join(__dirname, 'es6'),
    };
    gulp.task('babel', function () { // (A)
        return gulp.src(paths.es6)
            .pipe(sourcemaps.init()) // (B)
            .pipe(babel())
            .pipe(sourcemaps.write('.', // (C)
                      { sourceRoot: paths.sourceRoot }))
            .pipe(gulp.dest(paths.es5));
    });
    gulp.task('watch', function() { // (D)
        gulp.watch(paths.es6, ['babel']);
    });
    gulp.task('default', ['watch']); // (E)

In order to make gulp do something you invoke it like this:

    $ gulp «name_of_task»

Our gulpfile defines two tasks:

  • babel (line (A)) transpiles the ES6 files in es6/ to ES5 files in es5/.
  • watch (line (D)) continuously watches the ES6 files and transpiles them whenever they are changed.

If you call gulp without any arguments, the default task (line (E)) is triggered. In this file, the default task is watch.

Source maps are created due to the code in line (B) and line (C). If you omit the path in line (C), the source map is inlined in the output file (vs. stored in a separate file).

Hopefully you now have a rough understanding of how the gulp file works. For open questions, consult the gulp documentation.

Transpilation

The file es6/myapp.js contains the ES6 code of the Node.js application:

    import { install } from 'source-map-support';
    install();
    
    console.log([1,2,3].map(x => x * x));
    
    throw new Error('Test!');

Alas, Node.js does not come with built-in support for source maps. But it can be enabled via a library, e.g. the npm package source-map-support. That library needs to be called at least once in an app. The first two lines in the previous code takes care of that. They also demonstrate that you can use any npm-installed package via ES6 syntax.

The following gulp invocation transpiles myapp.js.

    $ gulp babel

Alternatively, you can use gulp or gulp watch to continuously watch the ES6 files and transpile them whenever they are changed.

The results of the transpilation are in the directory es5/:

    node-es6-demo/
        es5/
            myapp.js
            myapp.js.map

You can see the ES5 version of es6/myapp.js and the source map file myapp.js.map. The contents of the former file are:

    'use strict';
    
    var _install = require('source-map-support');
    
    _install.install();
    
    console.log([1, 2, 3].map(function (x) {
      return x * x;
    }));
    
    throw new Error('Test!');
    //# sourceMappingURL=myapp.js.map

Running the transpiled code

The transpiled code is a normal ES5 Node.js app and is run as usual:

    $ node es5/myapp.js

It produces the following output. Note that, thanks to the source map, the stack trace reports that the exception is thrown in line 6. That is the correct line in the ES6 file.

    [ 1, 4, 9 ]
    
    /tmp/node-es6-demo/es6/myapp.js:6
    throw new Error('Test!');
          ^
    Error: Test!
        at Object.<anonymous> (/tmp/node-es6-demo/es6/myapp.js:6:7)
        at Module._compile (module.js:456:26)
        at Object.Module._extensions..js (module.js:474:10)
        at Module.load (module.js:356:32)
        at Function.Module._load (module.js:312:12)
        at Function.Module.runMain (module.js:497:10)
        at startup (node.js:119:16)
        at node.js:906:3

4 comments:

Axel Rauschmayer said...

Have you tried out webpack tool for these purposes? I guess webpack's babel loader for es6 transpiling is way more convenient in use. https://www.npmjs.com/package/es6-loader
http://webpack.github.io/docs/using-loaders.html

Axel Rauschmayer said...

Do you know of a good way to use Babel to transpile only the features of ES6 that you don't have enabled?

For instance, I don't really want Babel to use its regeneratorRuntime if I'm using Node 0.12, because that Node already has support for generators when using --harmony [1]. Alternatively, I do want to use it to transpile classes, because that is not something that Node has support for yet.

[1] http://stackoverflow.com/a/28389178/400461

Axel Rauschmayer said...

Good question. I don’t know if Babel supports that, my guess would be that it doesn’t.

Axel Rauschmayer said...

You can specify which transforms you want to use with Babel, at least to some extent. See https://babeljs.io/docs/usage/transformers/ for a list of the different transforms. You can disable specific transforms completely with the --blacklist cli option (not sure what the corresponding syntax is for the gulp plugin).