ES6 Modules, Build Tools and Browser App Delivery

By Ryan Florence, published 2013-07-15

TL;DR Write for the future, use the tools of today.

Server-side dependency management is heaven

install and declare dependencies require in your code package up generalized code and publish it to one place (npm, gems, etc.)

Browser dependency management is not even close to solved, and terrible

multiple module formats multiple package managers package up generalized code, publish all over the place, people still can’t use it easily

This is important

We’ve got to solve this. When somebody’s got an angular directive, an ember component, a web component, or any old useful javascript library they need a simple way to share it. Node has had great success because of how simple npm is to use.

To use vendor code in node its this simple.

$ npm install backbone from the cli. var Backbone = require('backbone') in your app.

I want this for the browser. But first:

Three distribution use-cases

entire app delivered at once (general use case) partial builds loaded over time (still common) no build (totally valid, haters)

Application build strategies

configure and concat load up entire directories add explicit load order config trace dependencies programmatically (r.js, browserify) this is how server code works, it just doesn’t package it up generally the whole idea behind a build, create an environment of modules for the browser



App delivery graph

Given those application distribution use cases and build strategies, we have:

modules app distribution build strategy globals full concat, none AMD full, partial concat, dep tree, none CJS full concat, dep tree

CJS/globals can do partial app loading with $.getScript and friends, but not as part of the module definition.

In times past I argued for AMD since–as you can see–it meets every use case. AMD has gained a lot of traction but will never see complete adoption by library authors, package managers, and JavaScript environments.

Existing tools cannot handle each of these use cases. You have to subscribe to the format that your tools subscribe to, and so do the libs you depend on.

Globals, not declarative, can’t find dependencies with tools, bad AMD, ugly, not everybody will adopt it CJS, doesn’t meet all the app distribution use cases

So no matter what you pick for your application you’re going to have to have to be hacking around 3rd party module systems that don’t match yours.

Unless people shipped UMD.

WAT IS UMD?!

A long time ago a few of us came up with the saddest looking JavaScript ever called Universal Module Definition.

As its name implies, the module format works in pretty much every module environment.

If Backbone, Ember, moment, angular, and everybody else wrote their code with UMD then you could throw all the existing front-end tools at them. For example, you could:

Choose AMD, install scripts with bower and build with r.js Do #1 with no build at all Choose CommonJS, install scripts with npm and build with browserify Choose AMD, install scripts with npm, and build with r.js Choose no format and simply include script tags on the page Use component with anything that supports UMD.

I could do a table of the cartesian product between all the module formats, package managers, and build tools but you get the point. It supports all the existing tools and therefore supports all the application delivery use-cases.

But there is one problem. “umd” gets auto-corrected to “mud”, and its no lie. Here is the terrible ugly using backbone as an example:

(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD define('backbone', ['jquery', 'underscore'], function (jQuery, _) { return factory(jQuery, _); }); } else if (typeof exports === 'object') { // Node.js module.exports = factory(require('jquery'), require('underscore')); } else { // Browser globals root.Backbone = factory(root.jQuery, root._); } }(this, function (jQuery, _) { var Backbone = {}; // Backbone code that depends on jQuery and _ return Backbone; }));

You can see why this never caught on. It is terrible. Nobody should have to write that. Looking at it makes me want to pick up cursing and describe it more colorfully.

Two years ago I didn’t know what to do to get over that. But today, things are different.

Author in ES6, transpile to UMD, distribute everywhere

Enter ES6 modules. Library devs can author in ES6 modules, transpile to UMD and distribute everywhere (npm, bower, whatever).

authoring looks as good or better than CJS with destructuring its the future, you will eventually use this format anyway

All that’s left is to add UMD support to the es6-module-transpiler. Which machty and I are working on right now.

What this requires: Library authors to be on board

This could be an uphill battle with some people, but:

anybody who cares about browser modules will likely see the benefit eventually ES6 is going to land and people have a platform incentive to start authoring that way we can fork/shim their repos and then point package managers to the fork/shim (we already do this with bower all the time).

The future

Step 1: getting library authors to author in es6.

Step 2: getting package managers to support es6 and transpile for us.

Step 3: simply waiting for ES6 modules to land and doing nothing.

We can do this.

Follow the es6-module-transpiler fork to see when we get the UMD compiler finished, and I’ll follow up with some live examples.