comments

Start Using ES6/ES2015 In Your Project With Babel and Gulp

The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.

In Automate your front-end workflow with Gulp you learnt the basics of Gulp and how it can help you and your team save precious time by creating and sharing tasks. Tasks that can consist on copying files from one place to another, minifying, bundling, managing depedencies, running tests or transpiling ES6/ES2015 or even TypeScript to a version of JavaScript that is understandable by the browser.

An image of gulp that reads Automate and enhance your workflow with gulp

That’s a common problem in modern web development, you want to get started using ES6/ES2015 and enjoy all the juicy new features but you can’t just switch because not every ES6 feature has been implemented by browsers yet. The solution to this problem is to add an extra step to your build process that transpiles ES6 to a version of JavaScript (ES5) that every browser can understand.

You could transpile on-the-fly using Babel.js but it would destroy the user experience of your website. You could also do it manually every time before you check-in your code into source control but then you’d need to deal with merge conflicts on every commit.

A better approach is to automate the ES6 to ES5 conversion with a gulp task so that you can share it with your team and avoid the annoying merge conflicts. Additionally, your continuous integration and delivery servers could use that same gulp task, so it’s a win-win scenario.

So let’s see how you can get started using ES6 with the help of Babel and Gulp.

What is Babel?

Babel.js has become the de facto standard to transpile ES6/ES2015 code into ES5. It does more than that, it also let’s you start using other future features of the JavaScript language that are still bein evaluated by the standards committee and that will likely become a part of the language in the future (like decorators or async/await).

Babel is designed as a plugin system so that you can select which features you want to enable for your project. This means that if you want to be conservative you can enable only the features that are part of the ES specification today.

A Quick recap of the Previous Article

If you haven’t read the previous article on these series or can’t remember it then you might want to check it out since this blog post builds on top of it:

Using Gulp and Babel to Transpile ES6

The easiest way to enable ES6 in your project with gulp is by taking advantage of the gulp-babel plugin. You can install it via npm (if you don’t have node and npm in your dev environmen check this article that tells you how to do it):

PS> npm install --save-dev gulp-babel babel-preset-es2015

Note how in addition to gulp-babel we install the babel-preset-es2015 that configures babel to enable the use of all features within the ES6/ES2015 specification.

Once installed you can create a new task to handle the transpilation, we’ll call it js. Let’s add it to our gulpfile.js:

var babel = require('gulp-babel')

gulp.task('js', function() {
  return gulp
    .src('app/**/*.js') // #1. select all js files in the app folder
    .pipe(print()) // #2. print each file in the stream
    .pipe(babel({ presets: ['es2015'] })) // #3. transpile ES2015 to ES5 using ES2015 preset
    .pipe(gulp.dest('build')) // #4. copy the results to the build folder
})

And now you can run the js task by using gulp js. Let’s create a javascript file that contains ES6 code so that we can verify that indeed everything works as expected. We will modify the app/js/codes.js and app/js/more-codes.js files that we used in the previous article on the series and that you can find on GitHub:

//
// codes.js
//
export class TapeMachine {
  constructor() {
    this.recordedMessage = ''
  }
  record(message) {
    this.recordedMessage = message
  }
  play() {
    console.log(this.recordedMessage)
  }
}

//
// more-codes.js
//
import { TapeMachine } from './codes'

const tp = new TapeMachine()
tp.record('Hello... Hellooooo!!! Helloooooo!!!!!')
tp.play()
// => Hello... Hellooooo!!! Helloooooo!!!!!

After running gulp js we can see how ES6 syntax and keywords have been transpiled to ES5 in build/app/js/codes.js:

//
// codes.js
//
'use strict';

Object.defineProperty(exports, "__esModule", { value: true });
var _createClass = function () { // etc...
function _classCallCheck(instance, Constructor) { // etc...

var TapeMachine = exports.TapeMachine = function () {
    function TapeMachine() {
        _classCallCheck(this, TapeMachine);

        this.recordedMessage = '';
    }

    _createClass(TapeMachine, [{
        key: 'record',
        value: function record(message) {
            this.recordedMessage = message;
        }
    }, {
        key: 'play',
        value: function play() {
            console.log(this.recordedMessage);
        }
    }]);

    return TapeMachine;
}();%

//
// more-codes.js
//
"use strict";

var _codes = require("./codes");

var tp = new _codes.TapeMachine();
tp.record("Hello... Hellooooo!!! Helloooooo!!!!!");
tp.play();
// => Hello... Hellooooo!!! Helloooooo!!!!!%

Notice how, by default, the ES6/ES2015 preset uses CommonJS modules. This is awesome if you want to write your JavaScript in the backend with node.js, but not so much if you want to run it in a browser.

The browser doesn’t understand CommonJS modules, in fact, it doesn’t understand any modules at all, that’s why we need to fallback to custom implementations that can take many different forms: IIFEs, AMD libraries like require.js, browserify, webpack, system.js, etc.

JavaScript module loading is a separate world of its own and it’s unfortunately out of the scope of this article but I’ll fix an article on that sometime soon!

So! We want to run ES6 in the browser and therefore we need to pick one of these options. In this article we are going to take advantage of system.js, the universal dynamic module loader, that has a much narrower application than tools like browserify and webpack. It only focuses of loading modules (ES6, CommonJs, etc) and requires an easier setup (so it let’s me introduce less new concepts and make this article simpler). We need to do a couple of things:

  1. Install a ES6 polyfill because system.js requires ES6 promises and generators. We would’ve need to do this anyhow because we ourselves want to be able to use these ES6 features as well. But didn’t Babel do that for me? You may be wondering right now? Well, Babel transpiles new ES6 syntax into ES5 valid code, but additional methods like Object.assign, new types like Promise or generators are provided in a separate javascript file as polyfills
  2. Install system.js and add a configuration for it

We can install all of these requirements with npm:

PS> npm install --save systemjs babel-polyfill

In this case system.js and babel-polyfill are going to be real dependencies and not development dependencies of our app as they will be part of our application in the browser. We will also create a task to copy the necessary third-party libraries to our build folder when we generate the app:

gulp.task('libs', function() {
  return gulp
    .src([
      'node_modules/systemjs/dist/system.js',
      'node_modules/babel-polyfill/dist/polyfill.js',
    ])
    .pipe(print())
    .pipe(gulp.dest('build/libs'))
})

And update our build task to contain the ES6 to ES5 transpilation and the copying of the third party libraries. If you remember from the previous article the build task used to look like this:

gulp.task('build', function() {
  return gulp
    .src('app/**/*.*')
    .pipe(print())
    .pipe(gulp.dest('build'))
})

Now it will look like this:

gulp.task('build', ['js', 'libs'], function() {
  return gulp
    .src(['app/**/*.html', 'app/**/*.css'])
    .pipe(print())
    .pipe(gulp.dest('build'))
})

Notice the ['js', 'libs'] array after the task definition. It means that this particular task build depends on two other tasks js and libs. These means that when we run build, the js and libs tasks will be run in parallel. Once completed, the content of the build task itself will be executed (in this case copying html and css files to the build folder).

In order to be able to test that system.js can load our ES6 modules let’s create an index.html in our root app folder and link our javascript files:

<!doctype html>
<html>
    <head>
        <script src="libs/polyfill.js"></script>
        <script src="libs/system.js"></script>
        <script>
        // set our baseURL reference path
        System.config({
            packages: {
                js: {
                    format: 'register',
                    defaultExtension: 'js'
                },
            },
            meta: {
                // our modules have been transpiled to commonJS
                'js/*': {format: 'cjs'}
            }
        });

         System.import('js/more-codes.js')
                     .then(null, console.error.bind(console));
        </script>
    </head>
    <body>
    </body>
</html>

This file references the required libraries polyfill.js and system.js and adds some configuration so that the module loader understand where to start loading ES6 modules (in this case js/more-codes.js).

We still need some sort of web server to test that everything is working. There’s yet another great plugin called gulp-webserver that can help with that:

PS> npm install --save-dev gulp-webserver

And we can create a serve task to spawn a web server, open the browser and be able to test our app:

gulp.task('serve', ['build'], function() {
  gulp.src('build').pipe(webserver({ open: true }))
})

If you run the gulp serve task, it will trigger a build, host your app and open the browser so you can see… well nothing. The only thing our script does is output something in the console. If you open the developer console you should be able to see:

Hello... Hellooooo!!! Helloooooo!!!!!

If we add the following lines at the end of more-code.js:

const p = document.createElement("p");
p.innerText = "Victory!";
document.querySelector("body").appendChild(p);

And run gulp serve again you’ll be able to see the fruits of your victory!.

Summary!

So this article has become faaaar longer than I had envisioned in the first place. I hereby declare that I am cutting it in 2 parts!

Today you’ve learned how to start using ES6 in your project by taking advantage of babel and gulp. You’ve learned that:

  • Babel.js is a tool that let’s you transpile ES6 code into ES5 code that can run in browsers
  • The gulp-babel plugin let’s you integrate babel in your gulp front-end pipeline
  • You can use the 2015 preset to get a basic configuration to enable all ES5 features
  • By default babel will produce CommonJS modules that can’t run in the browser
  • You can use the system.js module loader to load ES6 modules in the browser
  • In addition to transpiling your code you need to add the babel polyfill.js that contains ES6 polyfills (like Object.assign, Array.from, Promise, etc)
  • The gulp-webserver plugin is super useful to quickly create a web server and serve static files

In the next part of this article you’ll learn about source maps, adding watch tasks so that your ES6 is re-transpiled whenever you change some files, live reloading and enabling ES6/ES2015 in your favorite editor.

Want to Learn More About Gulp?

In the upcoming weeks I am going to be adding small elements to the build pipeline but if you can’t get enough of gulp here are some other interesting resources:

Interested in Learning More JavaScript? Buy the Book!

Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.

a JavaScriptmancy sample cover

Have a great week ahead!! :)

More Articles In This Series