Skip to content

Instantly share code, notes, and snippets.

@lukehoban
Created October 16, 2012 18:11
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lukehoban/3900992 to your computer and use it in GitHub Desktop.
Save lukehoban/3900992 to your computer and use it in GitHub Desktop.
ES6 module syntax alternatives
Things I want to be able to do:
a) Bind a name to a remote module
b) Alias a name for a module
c) Alias a name for a member of a remote module
d) Put all names from inside a module in lexical scope
e) Put all names from inside a remote module in lexical scope
f) Put a few names from inside a module in lexical scope
g) Define a local module
h) Export names from lexical scope
i) Re-export names from other modules
j) Re-export all names from another module
Note that (d) and (e) are likely to be removed from ES6. Per @dherman comments below, (d) is possibly a non-goal while (e) is just a postponed goal.
// #1 – Current (A)
a) import 'a' as a
b) module b = X.Y.Z
c) import { X: c} from ‘ide/code’
d) import * from X.Y.Z
e) import * from 'a';
f) import { d, e: e2 } from 'a'
g) module X { }
h) export { d, e: e2}
i) export { K: a.K } // I believe it is not possible to do this without also having the corresponding import line (a) above
j) export * from X.Y
Pros: Mostly shorter
Cons: Lots of new syntax (module, import, from, as, =, *), feeling of inconsistency
// #2 – Current (B)
a) module a = “a”;
b) module b = X.Y.Z
c) import { X: c} from 'ide/code'
d) import * from X.Y.Z
e) import * from "a";
f) import { d, e: e2 } from 'a'
g) module X { }
h) export { d, e: e2}
i) export { K: a.K } // I believe it is not possible to do this without also having the corresponding import line (a) above
j) export * from X.Y
Pros: Slightly more consistent than (A)
Cons: Still a fair bit of new syntax (module, import, from, =, *), still inconsistent, though less so
// #3 – An alternative proposal currently used by TypeScript
a) import a = module('a');
b) import b = X.Y.Z
c) import c = module('ide/code').X
d) import X.Y.Z
e) import module('a');
f) import { d, e: e2 } = module('a')
g) module X { }
h) export { d, e: e2}
i) export { K: module('a').K }
j) export * from X.Y
Pros: Consistent use of 'import' for all notions of import, limited new syntax (import, module, =)
Cons: Wordier with use of module('a')
@domenic
Copy link

domenic commented Oct 17, 2012

a) import a from 'a';
b) let b = X.Y.Z;
c) import { X: c } from 'ide/code';
d) kill it with fire
e) kill it with fire
f) import { d, e: e2 } from 'a';
g) module X { }

Only new syntax:

  • module for declarations (should never be used except by browser concatenators);
  • import x from y for all your importing needs.

@dherman
Copy link

dherman commented Nov 20, 2012

Hey Luke… I definitely see the appeal of your proposal. Some of the benefits:

  • consistency with the JS precedent of binding on the left-hand side of an = sign
  • the right-hand side defines a compositional "module path expression" language
  • possible to select an export in place with module('a').b.c, without the weirdness of 'a'.b.c or requiring two separate statements
  • consistent use of import as the binding form

Moreover, I'm working on modifying the design so that local modules are named by strings rather than variables, and they go directly in the current loader's registry. This lets you do local modules like so:

module "foo" {
    module "bar" { ... }
    import { d, e: e2 } = module("bar");
}

I know @domenic and others think import * is a tool of the devil, and it's off the table for ES6, but I still intend to future-proof for it, because I suspect people will miss it. So just thinking it through, allowing your syntax has the problem that it's impossible to tell syntactically whether an import binding names a module or a variable. This may sound innocuous, but it becomes a problem for bulk import, because it becomes very hard to deal with the possibility that a bulk import might actually bind a module used in a module path. For example, when you say import X.Y.Z; what happens if X.Y.Z defines a module called X? I won't go into details here, and there are many possible semantics, but they all have issues.

I had originally designed the syntax so that you could tell syntactically that a module binding was a module binding because it was always spelled module instead of import. That way you could compute the set of names in a lexical scope in two passes: first collect all the module bindings and bring them into scope, then resolve all the module paths and bring all their exports into scope—no danger of paradoxes like the one I described above.

Perhaps a way to fix the paradox with a syntax like your proposal is to disallow bulk imports from module expressions that start with a lexical variable. So your e) would be legal but not d). This might sound restrictive, but if you consider that local modules would now be given string names instead of variable names, you can still do:

module "X" { ... }
import module("X");

The only thing being disallowed is bulk import from an aliased module name. That seems less problematic. So in other words, I'm suggesting the following variation on your proposal #3:

d) disallowed
e) possibly allowed post-ES6
g) module 'X' { }

There are some more use cases I'd be interested to hear your thoughts on:

h) Export names from lexical scope
i) Re-export names from other modules
j) Re-export all names from another module

I'm guess you'd propose something like:

h) export { x: x, y: y }
i) export { x: module('a').x, y: module('b').y }
j) export module('a');

Is that what you were thinking?

Thanks,
Dave

@dherman
Copy link

dherman commented Nov 20, 2012

@domenic: Your a) doesn't actually do what a) is supposed to do, which is bind a name to the module itself, not one of its exports. Maybe you just don't really want to support a) since you believe module declarations should only be for concatenators. I imagine that's the major use case, although I think don't see why people wouldn't use local modules for lightweight code organization.

Dave

@lukehoban
Copy link
Author

@dherman: Yes, your line of reasoning makes sense. Cases (d) and (e) are the two that I had thought least about so far, both because they are being postponed from ES6 and because TypeScript similarly has not incorporated any syntax for these cases. But I do agree that designing the syntax with these cases in mind is a good idea.

I've updated the gist with a version of your h,i,j. I believe the moral equivalents of (i) and (j) in particular weren't possible with a single export expression in the earlier syntax proposals on the wiki?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment