Modules feedback from March 2013 meeting

James Burke jrburke at gmail.com
Tue Mar 26 13:26:27 PDT 2013


On Tue, Mar 26, 2013 at 3:23 AM, Andreas Rossberg <rossberg at google.com> wrote:
> On 25 March 2013 18:31, James Burke <jrburke at gmail.com> wrote:
>> ### Single Anonymous Export ###
> Also, optimising the entire syntax for one special use case while
> uglifying all regular ones will be a hard sell.

I believe this is one of the points of disconnect, at least with
people in the node and AMD communities. Single exports is regular
form, multiple export are seen as the special case.

But my main point with this section was: I was hoping that by turning
the syntax around (export for single anonymous, some other export with
label for multiple export) maybe that opened up some syntax options.
But syntax is hard, and I do not envy TC39's job to sort it all out.
Sorry if this was just noise.

> As I have explained earlier on this list, destructuring import and
> destructuring let are not the same. The former introduces aliases, not
> new stateful bindings. This is relevant if you want to be able to
> export mutable entities. So no, we cannot drop import.

Right, thank you for the reminder of the previous thread. I can see
mutable entities helping cycle cases. I am curious to know what else
it helps. But cycle cases are important, so that alone is nice.

I was hoping that with single export, since a mutable entity was a
special, new thing, it had more freedom to write the rules around it.
However, it seems different enough that it cannot not fit in with
`let` or `var`.

I wonder if this implies later assignment to the import name is not allowed:

import { decrypt } from 'crypto';
//This would be an error?
decrypt = function () {};

If so, that really drives home that it is a new special kind of thing.

In any case, thanks for your response. With that information, this is
the kind of summary I would give to node and AMD users:

* import exists because it creates something new in the language, a
reference to a mutable slot. This is really important for cycle
resolution. let and var cannot handle this type of mutable slot.

* multiple exports exists because it allows for better static
checking, and due to how import/export works with mutable slots,
allows cycles with those exports. While your community may not prefer
a multiple export style, there are others that do. Also, in some cases
there are "roll up" modules that aggregate an interface to multiple
module exports, and the multiple exports allows that to work even with
cycles.

* single anonymous export will be supported, so you can code all your
modules in that style and it all works out, and you even get better
cycle support when non-function exports are involved. (I have seen
cycles in node rely on function hoisting and strategically placed
require/module.exports assignment to work -- non-function exports are
harder to support with that pattern)

* node's imperative require is not deterministic enough for a general
loading solution, particularly for the web and network fetching. The
ES spec solves this by using string literals for dependencies that are
language-enforced to be top level, with System.load() for anything
that is computed dependency. The mutable slots provided by import give
robust cycle support.

* there are enough hooks in the Module Loader spec to allow node to
internally maintain its synchronous require, so it does not have to
force all modules to upgrade to a new syntax, and a good level of
interop with ES6 modules is possible.

* AMD's dependency resolution has the right amount of determinism, but
suffers from weak cycle support. It also less clear semantically since
require('StringLiteral') can be used in control structures like
if/else, but operates more like System.get('StringLiteral') --- it
just returns the cached module value, it does not trigger conditional
code loading. All require('StringLiteral') calls are effectively
"hoisted" to the top level for module loading purposes, which can be
surprising to the end user, particularly when coming from node.

* since the ES6 Module Loader can load scripts with the same browser
security rules as script tags (load cross domain without CORS, avoids
problems with eval, like CSP restrictions) then the need in AMD for a
function wrapper in single module per file source form goes away, and
you recover a level of indent.

* there are enough hooks in the Module Loader spec that a hybrid
AMD/ES6 loader can be made, so no need to force upgrade all your AMD
modules, it can be done over time. Since AMD's execution model aligns
pretty well with the ES6 model, it will be easy to write conversion
scripts.

>> ### Nested modules ###
> I agree with your goal, and that is why I still maintain my point of
> view that modules should be denoted by regular lexically scoped
> identifiers, like any good language citizen. Then we'd get the right
> rules for free, in a clean, declarative manner, and wouldn't need to
> reinvent the wheel continuously, through more and more operational
> hacks.

Some dependencies are referenced via strings, `from "some/thing"`.
Those string names need to be there for modules not built into the
local collection of modules. Having two ways to refer to modules seems
like one too many.

System.get() looks up via string IDs. Once in string space, it is best
to stay in string space. Otherwise, code rewriting rules are needed,
that rewritten code moves further away from the source form of the
code, and it still seems that a "string ID to lexical ID" lookup table
would be needed, to handle all the cases of imperative System.get()
usage.

James


More information about the es-discuss mailing list