ES6 modules

Ryan Zimmerman edited this page Jan 4, 2017 · 3 revisions

For a good introduction on how to write and use ES6 modules, see jsmodules.io.

Why use modules at all?

The benefits of module systems are many:

  • Code organisation: modules allow (or, perhaps, force) you to split your code up into logical chunks, rather than dumping everything in a single mammoth script file
  • Dependency resolution: no more manual ordering of <script> tags! You don't need to tell the browser that it needs to load Underscore before it can load Backbone - the module loader already knows
  • Collaboration: ever worked on a codebase with other people? Ever edited the same file as someone else, then spent longer resolving merge conflicts than you did writing the code in the first place? Modules largely solve this problem
  • Optimisation: a module system can intelligently bundle your code up into a single file for deployment

I could go on, but the fact that you're here suggests you probably already know about modules. Just to recap, there are two main flavours of module in JavaScript-land:

  • AMD (asynchronous module definition) - works with e.g. RequireJS
  • CommonJS - works in node.js or with Browserify

There's also UMD (universal module definition), which works as an AMD module, a CommonJS module, or as a browser global.

What's so great about ES6 modules?

For one thing, it's the first time we've had modules that are actually part of the language. Now that the standard has been formalised, we can look forward to a future in which browsers (and node.js, eventually) natively support ES6 modules. So code written in ES6 modules is future-proof.

Even if that wasn't the case, there's lots to like about ES6 modules. For a comprehensive overview, I'll defer to Axel Rauschmayer: ECMAScript 6 modules: the final syntax. Some of the features I particularly like:

  • The syntax is nicer - certainly nicer than AMD, which frankly looks a bit weird (I say that as a long time RequireJS advocate). You don't have to indent everything!
  • Cyclic dependencies are supported - no more futzing around with secondary require() statements inside your AMD module, or choosing between single function exports and putting everything on the exports object in CommonJS
  • Modules export bindings rather than values

The killer feature

There's one huge advantage ES6 modules have - multiple exports.

With CommonJS and AMD, you can only export one thing. Historically, we've worked around that by exporting an object with lots of things - for example, Underscore exports the _ object with lots of functions attached - _.where, _.memoize, _.pluck and so on.

That's a problem, because it's more or less impossible for a module bundler or minifier to say with any certainty which bits of Underscore code you're actually using - even though you might only be using a single function.

The ES6 module syntax is designed in such a way that a bundler can be much smarter about what it includes. That's where Rollup comes in.

ES6-friendly libraries

Right now, most libraries are not distributed as ES6 modules. If you install a package from npm, there's a good chance it uses CommonJS, but that's about as good a guarantee as you'll get.

For Rollup to work its magic, it needs the library to be written in ES6 modules. The library's package.json file should use the module field to point to the main file.

Until all the libraries you use are available as ES6 modules, you can use Rollup to generate a CommonJS or AMD bundle, which you can then feed into something like Browserify or the RequireJS optimiser. Obviously that sucks, but you can help by making your own packages ES6-compliant and lobbying the authors of your favourite libraries to do likewise!