A recent proposal to change ES6 modules slightly has unleashed a torrent of drama. What follows is a recap of the ES6 module system, followed by some background, followed by a parable, followed by my opinions.

ES6 Modules

The original module system for ES6 involved modules being able to export names via either putting export in front of declarations

export var/let/const foo = bar; export function foo(){}

or with export statments

function foo(){} export {foo} export {foo as bar}

these names could then be imported into another module

import {foo, bar as baz} from 'path'

if you needed to import an object with all of the names you could use

module name from 'path'

much work was put into making sure that circular dependencies and other edge cases were worked out leading to the restriction that import, module, and export statements must be at the top level so that they will always run, so no

if (something) { export let foo = bar }

There was also originally an ability to create inline modules via

Module name { // stuff }

but this was removed on the grounds that it was too complex for now and can be added later.

A major issue with this setup was that there are no anonymous exports, it is very common in node.js and require.js systems to have a module be a single function or an instance of a class, but with the original setup you would have to give a name to that single thing. You see this in python with super ugly stuff like “the foo module which exports a foo member” but just for fun also stuff like “the bar module which exports a Bar member” and leads to usage where people just use foo.foo and bar.Bar.

The last major change to modules before recently was the addition of ‘default exports’ which added the ability to export something without giving it a name via

export default function (){} //or function foo() {} export default foo

which is actually a more flexible syntax then the export from syntax which can only take a name

// good function foo(){} export {foo as bar} // bad export {function (){} as bar}

on that note the other thing you can’t do is

// bad function foo(){} export foo

but I digress these default exports could be imported with

import foo from 'path'

this was done about a year ago and always seemed to be as a bit of a stop gap as it left the module system in a somewhat inconsistent place where different ways of exporting functions meant they had to be imported differently, for instance with underscore which is a collection of methods, to get it’s map method you can either do

module _ from 'underscore' _.map();

or

import {map} from 'underscore' import {map as _map} from 'underscore'

though you can’t do

//BAD let _ = {}; import {map as _.map} from 'underscore'

now on the other hand for jquery which is also a collection of methods, but are attached to a function you would have to do

import $ from 'jquery' $.map();

because if you tried to do

module $ from 'jquery'

map would actually be at

$['default'].map()

the solution proposed by TC39 is to remove the module/from syntax so that you either use a default import or you import specific methods and if you need all the full module object do something like

import 'foo' let foo = this.get('foo')

if you can’t tell the idea seems to be that you would need this very often.

This change has spawned one of the most contentious threads on the es-discuss mailing list since somebody pointed out that Promise.resolve and Promise.cast were functionally identical. To understand why you have to remember that these aren’t the only JavaScript modules.

A lengthy digression on AMD and Common JS modules.

The JavaScript community currently uses 2 module systems, AMD and Common JS (CJS), while some people describe them as 'competing module systems’ those people are wrong. The CJS and AMD are for all intents and purposes the same module system used by 2 different groups. The major difference between the 2 systems is that CJS modules are individual files while AMD are JavaScript objects, a CJS module wrapped in

define(function (require, exports, module) { ... });

is an AMD module and while AMD modules do have some ways of importing and exporting code that CJS modules does not have, the trend has been (in my biased experience) away from using them.

These days when people talk about a AMD/CJS split they are usually meaning browserify/webpack/npm vs r.js/cujo.js/bower split.

The important thing to remember is that since inline modules were removed from the spec, the things that the ES6 spec is covering are the things that the AMD/CJS communities agree on, namely syntax and that in code a module is just an object.

Another Tangent

A coder spends his free time writing a module to download cat pictures, he publishes it to NPM. A user opens an issue about the possibility of using it to download dog pictures or maybe even generic mammal pictures. The coder feels that the user does not understand the purpose of this module and instead of adding the feature, or even just dismissively pointing out that pull requests are accepted, the coder argues with the user about whether the user really wants this.

This is a bad habit that many of us occasionally suffer from. The reason for it is that when you put a lot of work into writing something, its easy to to take people trying to offer suggests to your project as people attacking your code baby. If you publish your project then you’re no longer the only user of it and other people have different needs than you and need different features then you, this is natural.

My opinions

With modules I feel like TC39 is suffering from this syndrome as well. Instead of taking criticism as suggesting a need to work towards adapting the system based on the experience learned from actual JavaScript developers using modules in the wild, they seem to be taking criticism as something they need to defend against and this makes the node.js community feel like TC39 doesn’t care about them, because when they point out that they’ve learned a thing or 2 about modules in the past several years, TC39 seems to say 'we don’t care’.

People put a lot of hard work into making the module system, but having an inflexible take it or leave it approach does nobody any good. Modules have yet to be implemented by any engine so if comprehension, which firefox already implemented, can be taken out of the spec, then there is no reason that anything should be off the table regarding modules.