Babel + Grunt === ES6 Now!

I’m really excited about all the great new JavaScript language features in EcmaScript 6 and I’ve been trying to figure out how I could use it in my day-to-day JavaScript work. However, even the latest browser versions only support a subset of the ES6 standard and it will probably be quite a while before they support a majority of the features. Even then if you have to support older browser versions then using ES6 will be problematic. What is a developer to do? Fortunately, this is where transpilers and shims come to the rescue!

ES6 Today with Babel

I spent some time researching some of the different transpilers and shims available and decided to try out BabelJS in our build pipeline. I’ve been reading about other teams in the community who are actually using it in their production applications today and it seems to work well.

Babel enables the use of ES6 today by transpiling your ES6 JavaScript into equivalent ES5 source that is actually delivered to the end user browser. This means it takes valid ES6 and generates the ES5 equivalent JavaScript that works even in a lot of older browsers. This transpilation step occurs as part of your build process when you deploy your application. In this way it doesn’t require your users to load any additional JavaScript to support ES6 or place any of the performance burden for the transpilation on their device.

For more information about Babel’s supported feature set and compatibility you can review their faqs.

What is transpiling?

Most of us have heard of the term compiling, but transpiling is not as common of a term. What exactly is the difference? Transpiling is actually a type of compilation. Generally compilation refers to taking code in one language and converting it to the equivalent instructions in another language at a lower level of abstraction. While transpiling is comping to a language at the same level of abstraction. In the case of Babel it is transpiling JavaScript that complies with the ES6 standard into JavaScript that complies with the ES5 standard.

Babel allows you, even without shims, to use a majority of the language features available in ES6 today and still be compatible with a wide range of browsers (Babel has compatibility information on their site). Obviously there are some new features which cannot be used via transpiler, since they require underlying frameworks or JavaScript engines changes. These features aren’t available, but there are shim options for these listed in Babel’s documentation.

Tip: If a feature of ES6 isn’t working with Babel, check the documentation on their site and make sure it doesn’t require a browser shim. If a feature requires a shim, you can make a decision about whether the feature is worth loading additional JavaScript into your application to support it.

Install grunt-babel

We currently use grunt for the build pipeline of our application. Integrating Babel into this pipeline was pretty easy (they already have integrations for most of the popular JavaScript build tools). In this case we’re using grunt-babel. To add this to our project dependencies we just need to run:

$ npm install --save-dev grunt-babel

This adds the grunt-babel integration as a dev dependency to our npm packages.son. Grunt-babel is the grunt integration we need and it includes a dependency on the latest version of babel-core. So that’s all we need.

** Configure grunt-babel **

module.exports = {
    options: {
        sourceMap: true,
        stage: 0
    },
    files: {
        expand: true,
        src: ['**/*.es6'],
        ext: '-compiled.js'
    }
};

We’re using separate files for each of our different grunt configurations, so you can see the node module exporting syntax here. If you are using one large grunt file, this will probably start with babel = instead of module exports and will be inside of a grunt.initConfig. You can find details about how to configure grunt-babel on the github page of that project.

The options listed here are passed directly to Babel. In this case we are indicating we would like source map files generated (highly recommended because it makes it much easier to debug your ES6). We are also setting the transpiler to stage 0 which allows it to use experimental features (ones even outside the ES6 spec, think ES7).

The files node is using the normal grunt globbing patterns to expand the files. Basically it is going to recurse the entire application folder tree looking for any .es6 files, transpile them into ES5, and switch the extension .es6 to -compiled.js.

Gradual Migration

One of our core goals was to be able to gradually migrate our application’s JavaScript over to ES6 in a way that didn’t break existing functionality. We didn’t want to have to go through a large conversion effort and set up every file in the project using ES6 all at one time. Instead we set it up so that we could migrate each JavaScript source file over to ES6 independently. To accomplish this, we did the following:

1) Run babel against files in the local source directory.
It’s possible to have grunt transpile your ES6 files in your working directory and put the result into the distribution directory. This means the transpiled files only exist in the distribution and all of your local files are the ES6 source. We decided not to do this because we wanted to be able to see the ES6 source file alongside the transpile ES5 version. This also allows us to run our source code directly from our working folder for quick debugging and local development. Since the transpiled version and maps are in the source folder, it allows browsers that may not support all the ES6 features to run and debug the ES6 source.

Tip: If you are generating source maps it will allow you to use browser development tools to step through your ES6 source code, even though the browser is actually running the ES5 version. If you haven’t used source maps before (frequently they are used to allow debugging of bundled / minified JS source), you should definitely check them out.

Our grunt-babel config indicates that we should take any file in our entire project structure ending with .es6 (src: [‘**/*.es6’]) and compile it into .js and .map files with the -compiled suffix (ext: ‘-compiled.js’). This means we will have three files in the folder for each .es6 files once it has been transpiled:

Screen Shot 2015-07-03 at 2.11.43 PM

In this case WebStorm has associated these files for us and rolled them up under the .es6 file.

2) Early in the build pipeline
Files are transpiled very early in the build pipeline so that they can be minified, bundled, and cache busted later in the pipeline. This way the act exactly like the .js files we already have in the project. The fact that we are transpiling has no impact on the remainder of the build pipeline since the .js files will be picked up by the other steps and the .es6 files will be ignored.

3) Exclude transpiled files from source control
The .es6 files are the actual source code logic for our application. The transpiled ES5 files are just an artifact of our deployment process. As such they should not be in the source repository. They should be built as part of our build pipeline on the build server or by a local file watcher on change. If they are checked into source control there is the danger of using the incorrect artifact on another developers machine or from the build server.

Added to .gitignore:

*-compiled*

Other things you’ll want to consider

  • Switch the JavaScript source reference in your index.html files.
    If you have a module loader in place you probably won’t need to worry about this. However, if you are statically adding references to your JavaScript then you will need to change the file referenced to the -compiled.js so that it will loaded correctly.

  • Setup a file watcher to transpile your files as you work.
    WebStorm has support for file watchers and will even prompt you to setup a Babel file watcher when it notices you are working in ES6. The file watcher will transpile ES6 files as you work on them. It also displays any errors encountered during the transpile. The early you get this type of feedback, the better!

  • Use watch to update your files while debugging with livereload.
    Make sure to update your watch task to run babel every time an ES6 files is modified.

  • Consider using babel-eslint to lint your Babel ES6
    Eslint has the best support for linting ES6 and the Babel team has created a nice integration – babel-eslint.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s