Module naming and declarations

Andreas Rossberg rossberg at google.com
Wed May 8 08:05:51 PDT 2013


On 7 May 2013 21:48, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
> On Thu, May 2, 2013 at 2:13 PM, Andreas Rossberg <rossberg at google.com> wrote:
>> On 1 May 2013 01:15, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
>>> There are several reasons why module declarations are important.
>>>
>>> 1. Right now, every JS construct can be written at the REPL, in a
>>> script tag, in a bunch of files in a Node package, or anywhere else.
>>> This is a very important property for the language, and one we should
>>> maintain.  Without module declarations except as separate files, we
>>> would lose this uniformity.
>>>
>>> 2.  Being able to define multiple modules concisely in a single file
>>> is simply more convenient than having to separate them into multiple
>>> files. Even clunky old Java allows this simple convenience!
>>>
>>> 3. Serving modules as script tags, and defining modules in inline
>>> script tags, is likely to be important for a wide variety of use
>>> cases.
>>>
>>> 4. Concatenation.
>>
>> [...]
>>
>> But my broader point, of course, is that lexical modules address all
>> these cases just fine.
>
> My point was not to say that concatenation is a weak argument and that
> we need more.  You agree that these are valuable features.  However,
> concatenation support is a *required* feature. The world in which
> multiple http requests are free is not here, and not on the horizon.

You seem to believe otherwise, but I think you still need to explain
how any of the above cases is not sufficiently (or even superiorly)
supported by lexical modules + the loader API.

You keep asserting that lexical modules didn't work, but I think your
reasoning why that necessitated the new design has been conflating two
separate issues all along, and the conclusion is largely a
non-sequitur.

I believe what you really mean is that using lexical names *for
coordination* does not work well in general (at least not too well,
cf. global scope). That is unsurprising. Rather, I was surprised to
learn that you ever made this assumption. I always assumed that you
were intending to use some form of higher-level URL mechanism for
that. And the loader API had the necessary hooks for it.

In any case, I don't see how this observation necessarily implies
_anything_ for the form of module *declarations*. I suspect that your
thinking had very much to do with (naive) concatenation, but
concatenation without a tool will never be possible for modules
anyway. So it really is a totally moot point.

OK, let's get concrete and see how this works. Assume you are writing
some subsystem or library. There are three phases.

1. Development. Most modules will typically be in individual files,
that you import through external names. These names would either be
relative paths or logical names, maybe depending on whether they cross
library boundaries (I see both in e.g. the Node world). Logical names
would most likely require some minimal loader configuration of the
base URL.

Module declarations don't play a big role here, except if you want to
define some local auxiliary modules somewhere. In that case, you are
clearly better off with lexically scoped declarations, which (1)
provide cheap, reliable references, (2) don't pollute the global
namespace, (3) can be defined locally in other modules, and (4) don't
require a separate import declaration.

2. Deployment. Once you're ready to release, you want to bundle
everything into one file ("concatenation"). Either way, that requires
a tool.

With path-named module declarations, like in your design, the most
basic version of such a tool needs to be given a set of JS module file
paths and a base path. The resulting file contains the contents of all
individual files, wrapped into module declarations whose names
presumably are the tail fragments of the respective file paths
relative to the base path -- or the logical names you were already
using. That file is your deployed "package".

With lexical module declarations, the tool will be similar, but
distinguishes the set of module paths that you intend to export from
the package. The resulting file will again contain all individual
files wrapped into module declarations, but this time with lexical
names (generated by some means -- the details don't matter since there
is no intention of making them accessible; you could even wrap
everything into an anonymous module). All import file names referring
to non-exported modules are replaced by their lexical references. In
addition, the tool generates a couple of suitable calls to loader.set
for registering the exported modules. The names for those are picked
in the same manner as in the other approach.

The advantage of the latter approach is again that all intra-bundle
references can be (1) cheap and reliable and (2) don't pollute the
global namespace. At the same time, you are free to export as many of
the modules as you like. So this approach is strictly more powerful,
the user has control.

3. Usage. In both approaches, the bundled file you have created
contains a number of modules with external (logical) names. There are
two ways to fetch them in another program: (a) either through a script
tag, or (b) by calling loader.ondemand with the list of provided
names. Both ways work equally for either of the designs!


So assuming this is the concatenation scenario you have in mind, where
in this setup are path-named module declarations relevant? Where would
the programmer prefer them over lexical declarations? (Sure you can
come up with simple use cases where they provide some minor
convenience, but none of these cases would scale to anything
interesting, AFAICS.)

I hope your argument isn't that the bundling tool for the lexical
approach is more complicated. Because it is only marginally so, and
certainly far simpler than existing tools like e.g. AMD's optimizer. I
probably could write you a simple version in half a day.


Some individual responses below.

>>> This would be a coherent addition to the system, but is not a
>>> necessary one. Below, you point out some issues that would be
>>> potentially addressed by adding them.  However, multiple JS module
>>> systems are in use today, and with the partial exception of the
>>> TypeScript namespaces feature (which is a significantly different
>>> design), none of them has lexically-named modules in the sense you
>>> advocate.
>>
>> I do not think this observation is particularly relevant. As I said
>> already, emulating a module system within JS necessarily has much more
>> severe limitations than designing it as a language feature. In
>> particular, I'm not sure you even _could_ build a lexical module
>> system under those constraints. There is no reason to apply those
>> limitations to ES6 modules, though.
>
> It's certainly possible to build a system like this in a compile-to-JS
> language like Typescript, and I don't believe it's happened.

Yeah, OK, clearly compilation is in a totally different ballpark.


>>> Needing to explicitly register a module will certainly be common in a
>>> node-like system, where small modules are the norm. Which is the
>>> common case in general isn't a question we can answer today.  But I
>>> don't think that needing to *avoid* registering will be common.
>>
>> Pro JS code today certainly goes to great length to avoid polluting
>> the global name space, by using anonymous functions. I think the
>> situation is much more similar than you'd like it to be.
>
> Mostly, this is managed by using a single namespace that everything
> hangs of, as with jQuery. This strategy is easy to apply to the module
> name space as well.

"Mostly" only if you have no intention of also replacing the common
"module pattern" using IIFEs with real modules. I'd view the module
system as a failure if it couldn't replace that.


>> But ultimately, it's simply a question of the right default. Making
>> everything externally visible, let alone overridable, is, plain and
>> simple, the wrong default. I'm still puzzled why you'd argue
>> otherwise, since for very good reasons, you made the opposite choice
>> one level down, for the contents of a module. I fail to see the
>> qualitative difference.
>
> These are designed for different purposes.  Modules are intended to be
> abstractions, module names are designed to help manage collections of
> modules.

Sorry, but no. It's the same thing. You bundle together a number of
definitions, and some of those you want to make externally accessible
and others not. Collections of modules in a package are the same in
that regard as collections of values in a module, just on a larger
scale.


>>>>> While it's sad that everyone doesn't use the same filesystem ;), this
>>>>> is inevitable unless we force everyone to write *both* internal and
>>>>> external names explicitly for every module; that's clearly more
>>>>> user-hostile than the alternative.
>>>>
>>>> But you do not want to do that for every module! In fact, you rarely
>>>> need to explicitly define a module with an external name at all, at
>>>> least as I envision it. You only want to do that for a module that you
>>>> want to "export" from your current script, so to speak. Such "export"
>>>> (i.e., globally registering a module you defined textually) should be
>>>> a very rare operation to write manually (as mentioned, you don't
>>>> usually do the equivalent in AMD, for example).
>>>
>>> Anonymous module defintions are often used in AMD for modules that are
>>> *implicitly* named by the file they're in.  In ES6, that simply
>>> doesn't require a wrapper.
>>
>> Yes, my point? Thing is, in AMD experience, named module definitions
>> are neither common, nor recommended. You use separate files. Why
>> expect something different for ES6?
>
> Right, you use separate files and that gives you a bunch of
> string-named modules registered in the global modules registry.
> That's then compiled to explicitly named modules all in one file. That
> this is similar to our design should be unsurprising, since they're
> working on the same problem, and both systems care about
> concatenation.

My point was that those explicitly named module declarations are all
generated by a tool. So there is no need to support it by path-named
declaration forms. The tool can just as well generate calls into the
loader API, and nobody would care about the difference. See above.

/Andreas


More information about the es-discuss mailing list