JavaScript
Article

Transpiling ES6 Modules to AMD & CommonJS Using Babel & Gulp

By Ravi

ECMAScript 6 (a.k.a ECMAScript 2015 or ES6), the specification for next version of JavaScript has been approved and browser vendors are hard at work implementing it. Unlike the previous versions of ECMAScript, ES6 comes with a huge set of changes to the language to make it a good fit for the scale at which it is used today. Sitepoint has a number of articles covering these features.

Although browsers haven’t implemented all of the features yet, we can already take advantage of ES6 during development and convert it to a version that browser understands before shipping the application. Babel and Traceur are two of the leading transpilers used for this purpose. Microsoft’s typed superset of JavaScript, TypeScript can also be used as an ES6 transpiler.

I covered how ES6 can be used today to write Angular 1.x applications in one of my previous articles. In that article I used Traceur’s on-the-fly transpiler to run the application. Although it works, it is always better to transpile beforehand and reduce the amount of work to be done in the browser. In this article, we will see how the same sample application can be transpiled to ES5 and the modules into either CommonJS or, AMD using Babel to make it run on today’s browsers. Though the sample is based on Angular, the techniques of transpilation can be used with any valid ES6 code.

As ever, you can find the code to accompany this article on our GitHub repo.

The Importance of Modules

One of the key features in any language used to write large applications, is the ability to load different pieces of the application in the form of modules. Modules not only help us keep the code cleaner but they also play a role in reducing the usage of global scope. The contents of a module are not made available to any other module unless the other module explicitly loads it.

The importance of modules is not limited to applications. Even large JavaScript libraries can take advantage of the module system to export their objects as modules and the applications using the libraries import these modules as required. Angular 2 and Aurelia have started using this feature.

If you’d like a quick primer on using modules in ES6, please read: Understanding ES6 Modules

About the Sample Application

The subject of our sample application is a virtual book shelf. It consists of the following pages:

  1. Home page: shows a list of active books that can be marked as read, or moved to the archive.
  2. Add book page: adds a new book to the shelf by accepting title of the book and name of author. It doesn’t allow a duplicate titles.
  3. Archive page: lists all archived books.

The application is built using AngularJS 1.3 and ES6. If you look at any of the files in the app folder, you will see the keywords export and import used to export objects from the current module and to import objects from other modules. Now, our job is to use Babel’s Gulp tasks to convert these modules to one of the existing module systems.

But I’m Not Using Angular. I Just Want to Convert ES6 Modules to CommonJS/AMD

No worries! We got you covered. With a minor amount of tweaking the recipes demonstrated below can be used in any project involving ES6 modules. Angular is quite unimportant here.

Converting to CommonJS

CommonJS is a module system defined by the CommonJS group. It is a synchronous module system, in which the modules are loaded using the require function and exported using the exports property of the module object. The module object is expected to be available in all modules by default.

Node.js uses this module system, so it defines the module object natively and makes it available to your application. As browsers don’t have this object defined, we need to use a utility called Browserify to fill the gap.

Before we start, we will also need to install a few npm packages. These will enable us to use Babel and Browserify in conjunction with Gulp to convert our ES6 modules to one of the common module formats and package the application as a single file for the browser to consume.

  • gulp-babel — converts ES6 code into vanilla ES5
  • Browserify — lets you require('modules') in the browser by bundling up all of your dependencies
  • vinyl-source-stream — handles the Browserify module directly, avoiding need for gulp-browserify wrapper
  • vinyl-buffer — converts stream to a buffer (necessary for gulp-uglify which doesn’t support streams)
  • gulp-uglify — minifies files
  • del — lets you delete files and folders
  • gulp-rename — a plugin to let you rename files

You can get this lot by typing:

npm install gulp-babel browserify gulp-browserify vinyl-source-stream vinyl-buffer gulp-uglify del gulp-rename --save-dev

Now let’s start using these packages in our gulpfile.js. We need to write a task to take all ES6 files and pass them to Babel. The default module system in Babel is CommonJS, so we don’t need to send any options to the babel function.

var babel = require('gulp-babel'),
    browserify = require('browserify'),
    source = require('vinyl-source-stream'),
    buffer = require('vinyl-buffer'),
    rename = require('gulp-rename'),
    uglify = require('gulp-uglify'),
    del = require('del');

gulp.task('clean-temp', function(){
  return del(['dest']);
});

gulp.task('es6-commonjs',['clean-temp'], function(){
  return gulp.src(['app/*.js','app/**/*.js'])
    .pipe(babel())
    .pipe(gulp.dest('dest/temp'));
});

Hopefully there is nothing too confusing here. We are declaring a task named es6-commonjs which grabs any JavaScript files in the app directory and any of its sub directories. It then pipes them through Babel, which in turn converts the individual files to ES5 and CommonJS modules and copies the converted files into the dest/temp folder. The es6-commonjs task has a dependency named clean-temp, which will remove the dest directory and any files in it, before the es6-commonjs task runs.

If you want to make the code more explicit and specify the module system, you may modify usage of Babel as:

.pipe(babel({
  modules:"common"
}))

Now we can create a single bundled file from these individual files by applying Browserify and then minifying the output using the uglify package. The following snippet shows this:

gulp.task('bundle-commonjs-clean', function(){
  return del(['es5/commonjs']);
});

gulp.task('commonjs-bundle',['bundle-commonjs-clean','es6-commonjs'], function(){
  return browserify(['dest/temp/bootstrap.js']).bundle()
    .pipe(source('app.js'))
    .pipe(buffer())
    .pipe(uglify())
    .pipe(rename('app.js'))
    .pipe(gulp.dest("es5/commonjs"));
});

The above task has two dependencies: the first is the bundle-commonjs-clean task, which will delete the directory es5/commonjs, the second is the previously discussed es6-commonjs task. Once these have run, the task places the combined and minified file app.js in the folder es5/commonjs. This file can be referenced directly in index.html and the page can be viewed in a browser.

Finally, we can add a task to kick things off:

gulp.task('commonjs', ['commonjs-bundle']);

Converting to AMD

The Asynchronous Module Definition (AMD) system is, as the name suggests, an asynchronous module loading system. It allows multiple dependent modules to load in parallel and it doesn’t wait for one module to be completely loaded before attempting to load other modules.

Require.js is the library used to work with AMD. RequireJS is available through Bower:

bower install requirejs --save

We also need the Gulp plugin for require.js to bundle the application. Install the gulp-requirejs npm package for this.

npm install gulp-requirejs --save-dev

Now we need to write the tasks for converting the ES6 code to ES5 and AMD and then to bundle it using RequireJS. The tasks are pretty much similar to the tasks created in the CommonJS section.

var requirejs = require('gulp-requirejs');

gulp.task('es6-amd',['clean-temp'], function(){
    return gulp.src(['app/*.js','app/**/*.js'])
    .pipe(babel({ modules:"amd" }))
    .pipe(gulp.dest('dest/temp'));
});

gulp.task('bundle-amd-clean', function(){
  return del(['es5/amd']);
});

gulp.task('amd-bundle',['bundle-amd-clean','es6-amd'], function(){
  return requirejs({
    name: 'bootstrap',
    baseUrl: 'dest/temp',
    out: 'app.js'
  })
  .pipe(uglify())
  .pipe(gulp.dest("es5/amd"));
});

gulp.task('amd', ['amd-bundle']);

To use the final script on index.html page, we need to add a reference to RequireJS, the generated script and then load the bootstrap module. The bootstrap.js file inside app folder bootstraps the AngularJS application, so we need to load it to kick start the AngularJS application.

<script src="bower_components/requirejs/require.js" ></script>
<script src="es5/amd/app.js"></script>
<script>
  (function(){
    require(['bootstrap']);
  }());
</script>

Conclusion

Modules are a long overdue feature in JavaScript. They will be arriving in ES6, but unfortunately, their native browser support is currently poor. That does not however, mean that you cannot use them today. In this tutorial I have demonstrated how to use Gulp, Babel and a variety of plugins to convert ES6 modules to the CommonJS and AMD format that you can run in your browser.

And as for ES6? ES6 has gained a lot of attention in the community since it was announced. It is already used by several JavaScript libraries or, frameworks including Bootstrap’s JavaScript plugins, Aurelia, Angular 2 and several others. TypeScript has also added support for a handful number of ES6 features including modules. Learning about and using ES6 today, will reduce the effort required to convert the code in future.

  • kandros

    Thanks, i noticed the clean variable is declared as del but del is used insted later on in the code

    • James Hibbard

      Fixed. Thanks :)

      • http://sravi-kiran.blogspot.com Ravi Kiran

        Thank you guys!

        • http://careersreport.com Lorenza Neal

          Allow me to~ show you a excellent ways to earn a lot of extra money by finishing basic tasks from your house for few short hours a day — See more info by visiting >MY___|DISQUS|___ID|

  • Owen Densmore

    So why not just jspm? Zero cost of startup .. npm install. Transpiler included (babel or traceur). *Zero* workflow for dynamic development. Works fine with es5 libraries locally downloaded or in CDN. And bundles into a single file for production.

    Just finished a webgl mooc with 5 fairly complex assignments and worked fine. Converting to bundle took some effort due to creating an html page for usage rather than the System.import needed to get the ball rolling.

    • http://sravi-kiran.blogspot.com Ravi Kiran

      Owen, I started exploring JSPM these days and I need some time to understand it well.

  • http://SalaryNet30.com Latosha Burns

    `I’m` finally` getting 94`Dollars“` an hr,….It’s time to take some action` and you can join it too.It is easy way to get rich`.Three weeks from now you will wish you have started today….

    +++++++++++++>>> Vis!t My Pr0f1le:)“`

    %ghg

  • brownieboy

    Your CommonJS example failed for me because gulp itself was undefined. You’re missing the line:


    gulp = require('gulp'),

    in your var definitions.

    Also, with Babel 6, and hence the latest versions of gulp-babel, I think you now need to npm install the babel-preset-es2015 module separately, and then specify it your call to gulp-babel, like so:


    .pipe(babel({
    presets: ['es2015']
    }))

  • Alex

    Thank you, great article!

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.