Converting from AMD modules to ES6 modules

I’ve been looking into converting one of my projects from AMD modules to the new ES6 modules. The pros and cons I could see are:

Pros:

Cons:

To me, the pros seemed like it was worth investigating.

Converting SCEditor

I decided to create a test branch to experiment with and see if ES6 modules were worth doing. Converting the main code from AMD to ES6 modules was fairly quick and painless but converting the automated testing and code coverage proved a bit trickier.

Testing

Initially, I tried to use Rollup for everything including testing but eventually decided to use the Webpack dev server for testing as it has a bigger ecosystem.

To make things easier I’ve created a small dev-server script. The dev-server script will disable code coverage when run from the CLI as coverage slows bundle generation down and isn’t used during development.

For automated testing, SCEditor uses the grunt-contrib-qunit package which needs to load the JS from the Webpack dev server to work. To do it I’ve added a grunt task to which starts the dev-server script on a different port from the CLI port (to avoid conflicts when developing) and with code coverage enabled:

grunt.registerTask('dev-server', 'Dev server', function () {
    const done = this.async();

    require('./tests/dev-server').create(9001, true).then(done, done);
});

which works quite well.

Coverage

For code coverage, I decided to move from Blanket.js (now deprecated) to Istanbul which with the istanbul-instrumenter-loader package was fairly painless.

To get the coverage data back from the grunt-contrib-qunit you can use an alert which triggers a grunt event:

// Send Istanbul coverage to grunt
if ('_phantom' in window && window.__coverage__) {
    alert(JSON.stringify(['qunit.coverage', window.__coverage__]));
}

and then in the grunt file add an event handler:

grunt.event.on('qunit.coverage', function (data) {
    const Report = istanbul.Report;
    const Collector = istanbul.Collector;
    const collector = new Collector();

    collector.add(data);

    // Output coverage stats to the console
    console.log('\n\n\nCoverage:');
    Report.create('text').writeReport(collector, true);

    // Write the HTML report to the /coverage/html/ directory
    Report.create('html', {
        dir: './coverage/html'
    }).writeReport(collector, true);
});

Overall testing IMHO has ended up being slightly nicer with ES6 modules. Especially as you can now start the dev server script and inject the bundle URL anywhere to test with without needing to set up an AMD loader.

You could do the same with AMD modules but then you lose their only real advantage of not needing a build step.

Building

For building the release distributable SCEditor now uses Rollup instead of Webpack.

With Webpack there wasn’t really any difference in file size when comparing ES6 to AMD modules but with Rollup (thanks to scope hoisting) the file size reduced by ~2kb (from ~61kb to ~59kb) which was about ~1kb difference gzipped.

Also, thanks to Rollup’s scope hoisting the code can be split into smaller, more logical modules without affecting the release size.

The Rollup vs Webpack size difference is not huge and probably not a good enough reason alone for most people to switch. Because SCEditor doesn’t use any of the more advanced Webpack features I decided switching was worth it for SCEditor.

Webpack is still used for development though as it has tools like the dev server with a good ecosystem which Rollup lacks.

Conclusion

Should you migrate current code to ES6 modules? I’d say yes, it’s worth doing if you’re not targeting node and you’re already making other breaking changes. I wouldn’t make a breaking change just to switch to though.

For all new JS which targets the browser, I’d definitely recommend starting with ES6 modules.

If you’re targeting node I’d wait for native node support before switching, just to avoid having to transpile ES6 into CJS modules.

Comments