Transpiling ES6 Modules to AMD & CommonJS Using Babel & Gulp
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:
- Home page: shows a list of active books that can be marked as read, or moved to the archive.
- 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.
- 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.