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:
- Part of the JS language
- Are the future of JS modules
- Will be natively supported in the future
- Allows tree shaking
- Allows Rollup to do scope hoisting (merge multiple modules into one to behave like a single module)
Cons:
- Not currently supported by any browsers
- Requires a build step to do anything with them (AMD can be loaded from the browser with a loader script)
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