According to this gist, the modules break-out session at TC39 (a subset of the larger committee) decided to remove the
module x from "y";
syntactic form, citing confusion versus import x from "y"
(which is sugar for import { default as x } from "y"
). Let's call these two forms "module instance object form" (or MIO form) and "default import form".
The MIO form was mostly useful when importing from a module with many exports:
// Now no longer possible:
module m from "m";
m.x(); m.y(); m.z();
Instead, we now can do:
import { x, y, z } from "m";
x(); y(); z();
or
import "m";
const m = System.get("m");
m.x(); m.y(); m.z();
The latter has a large disadvantage of removing all compile-time checking of the imported bindings. So if the last line was typoed to include m.zz()
, this would no longer be a compile-time error. This drawback also applies to the old form. However, the old form still is slightly more statically verifiable, as it can be guaranteed robust against tampering with System.get
(which misguided souls could do, for example, to implement a poor-man's normalize hook). The differences are minor, but interesting.
I use several modules with many exports, despite the community propaganda surrounding the "single default export only!" school of thought. These include modules like Q, jQuery, Chai, Underscore/Lo-Dash, and Request. Take a look through npm's most-depended upon packages and check out how often they are multi-export. An additional large chunk comes from my usage of the Node standard library.
The Node standard library, in fact, has a good representative spread of modules:
- Modules like events which have only a single useful export, and probably should be using the default export form.
- Modules like querystring which are utility grab-bags that you often want only one or two exports from;
- Modules like crypto where sometimes you want a single item, and sometimes you want many items (e.g. someone getting random bytes, vs. someone doing cryptography).
- Modules like os or fs which you often want many exports from at the same time;
I'd encourage you to find other examples of each category; there are many.
For case 1, in ES6 default exports will be used.
For case 2, in ES6 the import { parse } from "querystring"
form, or more likely import { parse as parseQS } from "qs"
form, will make a good deal of sense.
For cases 3 and 4, things get tricky. Your alternative is now something like:
import {
hostname as osHostname,
type as osType,
platform as osPlatform,
arch as osArch,
totalmem as osTotalmem,
cpus as osCpus } from "os";
or
import "os";
const os = System.get("os");
// Now you can use `os.hostname()`, `os.type(),` etc.
The former possibility seems very unlikely to come into use, and we can probably ignore it. Depite the static verification benefits, it is simply too verbose to expect people to type. So let us turn our attention to the latter possibility.
Compare the latter possibility with const os = require("os")
. It is really terrible in comparison, especially if you have to do it a few times in each file. So I don't think authors will want to do that either. What will they do instead?
On the consumer side, you can counteract this by putting aside ES6 modules and going back to CommonJS or AMD. It's important to remember that at this point ES6 modules are simply a third format alongside existing ones, and have to compete for authoring attention in the marketplace. Having committee backing provides a marketplace advantage, and they can use a "third way" effect to attract people who are disatisfied with the current CommonJS vs. AMD schism, but these are only potential advantages, and not realized ones yet. Note that one of the primary benefits of ES6 modules over existing solutions, namely static verification, is explicitly removed in the scenario under question. In fact, it only applies to case 2 (and sometimes 3) of the above list.
On the producer side, to alleviate your consumers of this pain, you can fall back to simple default exports. That is, if you are authoring the fs module (or Q, or jQuery, or Underscore, or Lo-Dash), you can author them not to use multiple exports, but instead to use the CommonJS/AMD-esque style of simply default-exporting a large object with many methods and properties. Or, of course, you can refuse to transition to ES6; the pressures there are different for library authors vs. app-builders, but you can imagine that the large portion of library authors who also use Underscore/Lo-Dash as a dependency would experience both negative pressures.
It's hard to say which way this will go, but it seems at least possible, and indeed somewhat likely, that this will remove motivation to do the "correct" thing for multi-export modules, and instead encourage authors to fall back to default-exports only. This is the smallest delta from existing authoring: you simply change module.exports = _
to export default _
, instead of going and changing each _.foo = function (...) { ... }
to export function foo(...) { ... }
. And it gives consumers a single story: always use import x from "y"
, no matter what.
In this world, the static verification benefits of ES6 modules are completely removed; doing import { zip } from "underscore"
will not work, and instead simply import _ from "underscore"
combined with _.zip
usage will prevail. This of course brings us back to the question of why people would switch from existing systems to ES6 modules, but let's assume they're locked in to that technology via e.g. tooling like ember-cli.
@locks, they are equivalent.