permalink

6

Author In ES6, Transpile To ES5 As A Build-step: A Workflow For Grunt

Preface: This article assumes prior knowledge of Grunt, the JavaScript task runner and that you've heard of Traceur, the ES.next to ES5 transpiler by Google. Getting started guides are available for both projects. For information on the current state of ES6, read the current working draft spec.

Today I used a Grunt task called grunt-traceur by Aaron Frost to author code in ES6 (maximally minimal classes, modules, rest params, spread operators) and transpile back to ES5 as a build-step. It felt fantastic and the final product now runs in all modern browsers.

I also played with grunt-es6-module-transpiler which let me target CommonJS or AMD as readily usable compile-targets for my modules. Both projects enable us to use JavaScript.next features in our apps before support actually lands in browsers. Judging by the ES6 compatibility table by @kangax this may still take a while so transpilation is an interesting option worth considering.

I've previously used the Traceur library and bootstrap files (which were script includes) to dynamically interpret ES6 at runtime, but being able to introduce it as a build-step is a welcome addition to my workflow.

Note: ES6 is an evolving draft and anything introduced in it will be experimental. Whilst authoring in it via a transpiler is feasible at this point in time, do take care as any syntax and semantics you rely on may be subject to change. The list of language features supported by Traceur is available for review.

Getting started with grunt-traceur just takes a few minutes to get setup:

  1. Start a new Grunt project. I used Yeoman to scaffold out a basic app structure for me with yo webapp
  2. Run: npm install --save-dev grunt-traceur.
  3. If not using Yeoman, Add grunt.loadNpmTasks('grunt-traceur'); to your Gruntfile.js. This isn't needed if using the webapp template, because it will load your grunt tasks for you.
  4. Add the following to your grunt.initConfig in the same file:

With Yeoman:

traceur: {
    custom: {
        files:{
            'build/': ['<%= yeoman.app %>/scripts/{,*/}*.js']
        }
    },
}

Without:

traceur: {
    custom: {
        files:{
            'build/': ['js/**/*.js']
        }
    },
}

You can now author using ES6 and run grunt traceur to transpile your scripts back to ES5. Alternatively, include in your build task grunt.registerTask('build', ['clean', 'traceur', '...']);.

Bonus: to save myself time, I just saved the following as a Sublime build system so I can transpile ES6->ES5 within my code editor without needing to go back to the terminal:

{
    "cmd": ["grunt", "traceur", "--no-color"],
    "selector": ["source.js"],
    "path": "/usr/local/bin"
}

grunt-es6-module-transpiler

An alternative to using Traceur, if you're only interested in modules, is the Square ES6 module transpiler which can target CommonJS and AMD.

Joe Fiorini put together a Grunt task for it that can be used similar to the above in his grunt-es6-module-transpiler project.

  1. Create a new project: yo webapp
  2. Run npm install --save-dev grunt-es6-module-transpiler.
  3. If not using Yeoman, Add grunt.loadNpmTasks('grunt-es6-module-transpiler'); to your Gruntfile.js
  4. Add the following to your grunt.initConfig:

With Yeoman:

transpile: {
    main: {
      type: "rjs", // or "cjs" for CommonJS
      files: [{
        expand: true,
        cwd: '<%= yeoman.app %>/lib/',
        src: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        dest: '<%= yeoman.app %>/tmp/'
      }]
    }
  }

Without:

  
transpile: {
    main: {
      type: "rjs", // or "cjs" for CommonJS
      files: [{
        expand: true,
        cwd: 'lib/',
        src: ['**/*.js'],
        dest: 'tmp/'
      }]
    }
  }

You can now manually run grunt transpile or include as part of your build task with grunt.registerTask('build', ['clean', 'transpile', '...']);.

Alternatives

The ES6 Modules now project may also be of interest but at this time is a script-include. It uses es6-module-transpiler by Square covers earlier and my the ES6 module loader shim I published a while back.

Notes

  • I've also enjoyed trying out TypeScript lately – classes, modules and other features like interfaces are supported and Grunt tasks for transpiling TS->JS as a build-step are available. There are one or two two tutorials on how to use grunt-typescript available, so check those out if they are of interest.
  • For those curious about how one debugs code in this workflow, Traceur (as a script-include) does have support for Source maps but this is a feature grunt-traceur does not currently appear to support. The author of grunt-traceur does state that it's coming.

Thanks to @passy and @sindresorhus for tech reviewing this post.

6 Comments

  1. Nice taste of the future. However I test compiled some code (with one class extending another) – under Chrome 26 the compiled code complains about not able to find “Object.getPropertyDescripter” method. Had to shim it with the following to get it working:

    Object.getPropertyDescriptor = function(subject, name) {
    var pd = Object.getOwnPropertyDescriptor(subject, name);
    var proto = Object.getPrototypeOf(subject);
    while (pd === undefined && proto !== null) {
    pd = Object.getOwnPropertyDescriptor(proto, name);
    proto = Object.getPrototypeOf(proto);
    }
    return pd;
    }

    • Thanks for sharing. Could you post an issue about this to the Traceur GitHub repo so that it can be patched upstream?

  2. Thanks for the article. I’m having some trouble after running “grunt”.

    Gruntfile.js
    ________________________________________

    module.exports = function(grunt){

    grunt.initConfig({
    pkg: grunt.file.readJSON(‘package.json’),
    traceur: {
    custom: {
    files: {
    ‘build/’: ['public/js/**/*.js']
    }
    }
    }
    });

    grunt.loadNpmTasks(‘grunt-traceur’);

    grunt.registerTask(‘default’, ['traceur']);

    }

    test.js
    ________________________________________

    class Greeter{
    constructor(message){
    this.message = message;
    }
    greet(){
    let version = “6″;
    console.log(this.message + ” ” + version);
    }
    };

    let greeter = new Greeter(‘Hey Ecmascript’);
    greeter.greet();

    Terminal
    ________________________________________
    ~/Sites/testingTraceur/app: grunt
    Running “traceur:custom” (traceur) task
    Missing URL
    build/ [ 'public/js/test.js' ]
    Compiling… build/
    public/js/test.js:6:3: ‘}’ expected
    public/js/test.js:6:15: ‘(‘ expected
    public/js/test.js:6:17: ‘)’ expected
    public/js/test.js:6:20: ‘{‘ expected
    public/js/test.js:11:1: primary expression expected
    public/js/test.js:11:5: Semi-colon expected
    Compilation failed – build/

    Done, without errors.

    Any help would be awesome!

  3. Pingback: Tracking ES6 Support In Browsers

Leave a Reply

Required fields are marked *.