Module syntax

# Axel Rauschmayer (11 years ago)

Bikeshedding: I generally like the module syntax, but I’d always omit the braces and use import default for importing the default export of a module.

Rationale: – Reducing the grawlix factor. – Making non-default imports more convenient – which I assume will happen more often(?)

# Rick Waldron (11 years ago)

On Mon, Jun 3, 2013 at 1:29 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

Bikeshedding: I generally like the module syntax, but I’d always omit the braces and use import default for importing the default export of a module.

From the wiki:

module "foo" { export default function() { console.log("hello!") } } ... import default;

What would be the bound name? foo? Am I missing something?

# Axel Rauschmayer (11 years ago)

On Mon, Jun 3, 2013 at 1:29 PM, Axel Rauschmayer <axel at rauschma.de> wrote: Bikeshedding: I generally like the module syntax, but I’d always omit the braces and use import default for importing the default export of a module.

From the wiki:

module "foo" { export default function() { console.log("hello!") } } ... import default;

What would be the bound name? foo? Am I missing something?

I’d use: import default foo from "foo";

Thus, the examples in the wiki [1] become: import default $ from "jquery"; // import the default export of a module module crypto from "crypto"; // binding an external module to a variable import encrypt, decrypt from "crypto"; // binding a module's exports to variables import encrypt as enc from "crypto"; // binding and renaming one of a module's exports export * from "crypto"; // re-exporting another module's exports export foo, bar from "crypto"; // re-exporting specified exports from another module

[1] harmony:modules

# Rick Waldron (11 years ago)

On Mon, Jun 3, 2013 at 2:40 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

On Mon, Jun 3, 2013 at 1:29 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

Bikeshedding: I generally like the module syntax, but I’d always omit the braces and use import default for importing the default export of a module.

From the wiki:

module "foo" { export default function() { console.log("hello!") } } ... import default;

What would be the bound name? foo? Am I missing something?

I’d use: import default foo from "foo";

How is this better then the existing:

import foo from "foo";

For importing a default export?

# Axel Rauschmayer (11 years ago)

On Jun 3, 2013, at 20:41 , Rick Waldron <waldron.rick at gmail.com> wrote:

On Mon, Jun 3, 2013 at 2:40 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

On Mon, Jun 3, 2013 at 1:29 PM, Axel Rauschmayer <axel at rauschma.de> wrote: Bikeshedding: I generally like the module syntax, but I’d always omit the braces and use import default for importing the default export of a module.

From the wiki:

module "foo" { export default function() { console.log("hello!") } } ... import default;

What would be the bound name? foo? Am I missing something?

I’d use: import default foo from "foo";

How is this better then the existing:

import foo from "foo";

For importing a default export?

It’s worse. The point would be to make non-default imports simpler and lose the braces (which make less sense with the new non-literal syntax) in those cases. That is: it makes default imports less convenient in order to make non-default imports more convenient and less grawlixy.

Compare – wiki: import $ from "jquery"; // import the default export of a module module crypto from "crypto"; // binding an external module to a variable import { encrypt, decrypt } from "crypto"; // binding a module's exports to variables import { encrypt as enc } from "crypto"; // binding and renaming one of a module's exports export * from "crypto"; // re-exporting another module's exports export { foo, bar } from "crypto"; // re-exporting specified exports from another module Brace-free: import default $ from "jquery"; // import the default export of a module module crypto from "crypto"; // binding an external module to a variable import encrypt, decrypt from "crypto"; // binding a module's exports to variables import encrypt as enc from "crypto"; // binding and renaming one of a module's exports export * from "crypto"; // re-exporting another module's exports export foo, bar from "crypto"; // re-exporting specified exports from another module Syntactic ambiguities may be a problem, though...

# Axel Rauschmayer (11 years ago)

We may be able to have both: convenient default imports and convenient non-default imports, if we bring back export = (which, IIRC, has been proposed a while ago) and introduce import = as an analog. Example:

module "jquery" { export = { ... }; }

import $ = "jquery";

Not completely happy with it (as both look like assignments), but it complements the non-default case nicely and everything combined may be the best we can do overall.

Remaining changes – wiki: import { encrypt, decrypt } from "crypto"; // binding a module's exports to variables import { encrypt as enc } from "crypto"; // binding and renaming one of a module's exports export { foo, bar } from "crypto"; // re-exporting specified exports from another module Brace-free: import encrypt, decrypt from "crypto"; // binding a module's exports to variables import encrypt as enc from "crypto"; // binding and renaming one of a module's exports export foo, bar from "crypto"; // re-exporting specified exports from another module

# Brendan Eich (11 years ago)

I was corresponding with Axel about what's possible without ambiguity, and he pushed for brace elimination noting that 'default' is a reserved word. This led to me remembering the export = ... form, more concise than export default ... -- not that "more concise" must win, but it seems worth a brief discussion. The export = form also looks more like today's module.exports = ... from NPM/AMD.

# Domenic Denicola (11 years ago)

I really dislike export =. It looks like it should be creating a global variable named export.

export default is perfect IMO. It also conceptually fits better with how default exports work. (I.e., they're default exports, they're not the entirety of the exports. They don't overwrite the module instance object, in the fashion that module.exports = does.)

# Rick Waldron (11 years ago)

On Mon, Jun 3, 2013 at 3:57 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:

I really dislike export =. It looks like it should be creating a global variable named export.

export default is perfect IMO. It also conceptually fits better with how default exports work. (I.e., they're default exports, they're not the entirety of the exports. They don't overwrite the module instance object, in the fashion that module.exports = does.)

Agreed. I was drafting a response when this came through and it expresses everything I was already writing.

Anything that "looks like an assignment", but isn't, is subjectively a really bad idea.

# Kevin Smith (11 years ago)

export default is perfect IMO. It also conceptually fits better with how default exports work. (I.e., they're default exports, they're not the entirety of the exports. They don't overwrite the module instance object, in the fashion that module.exports = does.)

A potential problem with export default is that there is very little to signify (to humans) that we are entering an expression context. For example, the following "looks like" a declaration with a modifier, but it's not.

export default function x() {}
x(); // ReferenceError!

It might be better to clearly indicate the expression context by a "=" or something similar.

# Andrea Giammarchi (11 years ago)

+1

as soon as the syntax is slightly different from NPM/AMD makes sense to have less ambiguity and a better definition.

export default looks good here too

# Andrea Giammarchi (11 years ago)

if

function x() {}
export default x;

works thought, that ain't any different from returning function expression where you cannot reuse that function if not inside the function itself.

It does not look like a big lost but more similar to expression logic we are use to with JS ... or, is it?

# Juan Ignacio Dopazo (11 years ago)

Now that it's been decided that braces are not a form of destructuring and the colon replaced with as, what's the benefit of using braces? Why not this previous proposal?

import foo as foofoo from "foo";

import "bar" as bar;

Juan

# David Herman (11 years ago)

On Jun 3, 2013, at 1:55 PM, Juan Ignacio Dopazo <dopazo.juan at gmail.com> wrote:

Now that it's been decided that braces are not a form of destructuring and the colon replaced with as, what's the benefit of using braces? Why not this previous proposal?

import foo as foofoo from "foo";

import "bar" as bar;

Because it's ambiguous with default import; without curly braces, a single named import, without the "as" renaming, would have indistinguishable syntax from importing the default export.

# David Herman (11 years ago)

On Jun 3, 2013, at 10:29 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

Bikeshedding:

Well, here comes a fun thread...

Rationale: – Reducing the grawlix factor. – Making non-default imports more convenient – which I assume will happen more often(?)

That's what this comes down to: do you expect the common case to be small modules with a main export, or large modules with many named exports. We opted for the former, since there are many prominent libraries and communities in the JS world that have gravitated towards small modules. That said, the extra burden of two curly braces is very, very small.

# Dmitry Soshnikov (11 years ago)

On Mon, Jun 3, 2013 at 1:55 PM, Juan Ignacio Dopazo <dopazo.juan at gmail.com>wrote:

Now that it's been decided that braces are not a form of destructuring and the colon replaced with as, what's the benefit of using braces? Why not this previous proposal?

import foo as foofoo from "foo";

import "bar" as bar;

I'm predicting thousands of "why" here once it's shipped. Users will be asking -- what the heck is going on here?

IM(h)O, it should be just:

import bar;

And that's it.

Below are some "syntax of my dreams" (not gonna bikeshead though):

Basic imports:

  1. import foo; // imports exports of module foo and binds to foo; can be used as foo.bar();
  2. import "mysite.com/foo.js" as foo; // imports foo from external source, can be done only via aliasing to bind name;
  3. import default foo; // import the default expression from module foo.

Aliasing:

  1. import foo as myFoo;
  2. import "mysite.com/foo.js" as myFoo; // already mentioned

Import all bindings to the current ns:

  1. import * from foo; // can be used then as bar(); baz();
  2. import * from "mysite.com/foo.js; // the same

Import some of the methods:

  1. import bar from foo; // imports only bar method from foo (w/ potential syntax ambiguity and using {})
  2. import {baz, bar} from foo; // ditto, two
  3. import {baz: myBaz} from foo;
  4. import bar from "mysite.com/foo.js";

Please, please, please -- would be awesome to NOT using string names (if of course possible):

module foo {} // doesn't matter whether it's lexical or not, but foo is just a name

module foo/bar {} // namespaced name, OK, can be module 'foo/bar' {}

P.S.: Although, I don't like bikesheading threads and think it should be prepared well with arguments and all implementation details before discussions.

Dmitry

# Kevin Smith (11 years ago)

Bikeshedding:

Well, here comes a fun thread...

Bikeshedding is fun : )

FWIW, I've implemented a parser for curly-free syntax and it does introduce some minor parsing difficulties on the export side. If we had lexical modules, then this:

export module as ...

could be a valid prefix for an exported module declaration or an export set. You basically need more tokens of lookahead to disambiguate on these contextual keywords.

I'm totally happy with the current syntax, modulo some concern over the default export syntax which I mentioned upthread.

# Jeff Morrison (11 years ago)

import foo from "foo"; reads to the quick eye as "import the symbol named foo from the module named 'foo'" -- but that's not actually what it means (wrapped curlies be damned!). I think the syntax would read much less confusingly if we made use of a default keyword to signify better what's happening in this declaration...

Anything from import default foo from "foo" to the more preferable (in my mind) import default as foo from "foo"

# Kevin Smith (11 years ago)

It occurs to me that this is valid under the current grammar:

import { default as foo } from "foo";
export { foo as default };

We've discussed using a well-known symbol for the default export, but this simple desugaring might be another option:

import foo as "foo";
// => import { default as foo } from "foo";

export default = expr;
// => let __genident__ = expr; export { __genident__ as default };

This would provide easy access to the default export on a module instance object without having to obtain a reference to a symbol.

module Foo from "foo";
F.default();

Just throwing an idea out...

# Kevin Smith (11 years ago)

Sorry - corrections to examples:

import foo from "foo";
// => import { default as foo } from "foo";

export default = expr;
// => let __genident__ = expr; export { __genident__ as default };

module Foo from "foo";
Foo.default();

Thanks,

# David Herman (11 years ago)

On Jun 5, 2013, at 11:51 AM, Kevin Smith <zenparsing at gmail.com> wrote:

It occurs to me that this is valid under the current grammar:

import { default as foo } from "foo";
export { foo as default };

We've discussed using a well-known symbol for the default export, but this simple desugaring might be another option:

import foo as "foo"; 
// => import { default as foo } from "foo";

export default = expr; 
// => let __genident__ = expr; export { __genident__ as default };

This would provide easy access to the default export on a module instance object without having to obtain a reference to a symbol.

module Foo from "foo";
F.default();

Just throwing an idea out...

Yes, this is actually the direction I've been going in my thinking, based on the critique that export default is the only export form that isn't a binding form, especially when combined with a named function literal (export default function f() { ... }). I also like the conceptual simplicity of the default export simply being a public export named "default". Moreover, Yehuda has urged me to consider

export x = 17;

as sugar for

export let x = 17;

When you put this all together, you can actually just see export default = ... as sugar for export let default = .... We can also allow the keyword default to be used for the other exported declaration forms like export function default(...) { ... } and export class default { ... }. (We'll finesse the .name property of such a declared function to be taken from the module name, a la Brandon's accepted proposal for .name in ES6.) To recap, the following all become valid ways to specify the default export:

export let default = 17;
export default = 17;
export default = function f() { ... };
export default = class { ... };
export function default(...) { ... }
export class default { ... }

And you can now import both the default and non-default exports of a module in a single line:

import { default as $, ajax } from "jquery";
# Matthew Robb (11 years ago)

I really like the way this sounds, just wanted to submit my approval :D

# Domenic Denicola (11 years ago)

From: David Herman [dherman at mozilla.com]

Moreover, Yehuda has urged me to consider

export x = 17;

as sugar for

export let x = 17;

I'd urge const instead of let, as const discourages the footgun of action-at-a-distance mutable with/global-like bindings that I keep talking about (e.g. at esdiscuss.org/topic/importandaliasingbindings). Ideally this would extend to function and class as well. The desugarings then become

export x = 17;
export function x() { };
export class x { };

to

export const x = 17;
export const x = function x() { };
export const x = class x { };

In fact, if I had my way I'd continue down this path trying to eliminate mutable action-at-a-distance bindings entirely by removing export let, export var, and export const. That just leaves let x; export { x }; as dangerous, and I think there are solutions to that but I won't bore you with them unless people are enthusiastic about this idea...

# Axel Rauschmayer (11 years ago)

This makes a lot of sense. It would obviate the need for braces, right?

# David Herman (11 years ago)

On Jun 5, 2013, at 3:59 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

This makes a lot of sense. It would obviate the need for braces, right?

No, at least not for imports. This is only about exports. I'd argue we should keep the braces on export { x, y, z } for symmetry with imports.

# David Herman (11 years ago)

On Jun 5, 2013, at 2:58 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

From: David Herman [dherman at mozilla.com]

Moreover, Yehuda has urged me to consider

export x = 17;

as sugar for

export let x = 17;

I'd urge const instead of let, as const discourages the footgun of action-at-a-distance mutable with/global-like bindings that I keep talking about (e.g. at esdiscuss.org/topic/importandaliasingbindings). Ideally this would extend to function and class as well.

This is too bondage-and-discipline for JS. If you simply don't multiply-assign to the bindings they will be immutable anyway.

In fact, if I had my way I'd continue down this path trying to eliminate mutable action-at-a-distance bindings entirely by removing export let, export var, and export const. That just leaves let x; export { x }; as dangerous, and I think there are solutions to that but I won't bore you with them unless people are enthusiastic about this idea...

I'm not. :) I don't agree that the ability for a module to choose to export a mutable binding is bad.

# Axel Rauschmayer (11 years ago)

On Jun 6, 2013, at 19:42 , David Herman <dherman at mozilla.com> wrote:

On Jun 5, 2013, at 3:59 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

This makes a lot of sense. It would obviate the need for braces, right?

No, at least not for imports. This is only about exports. I'd argue we should keep the braces on export { x, y, z } for symmetry with imports.

OK, so you don’t want to replace

import foo from "single_export_module";

with (braceless!)

import default as foo from "single_export_module";
# David Herman (11 years ago)

On Jun 6, 2013, at 11:05 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

OK, so you don’t want to replace

import foo from "single_export_module";

with (braceless!)

import default as foo from "single_export_module";

That's correct, I don't want to use import default.

# Kevin Smith (11 years ago)
export let default = 17;
export default = 17;
export default = function f() { ... };
export default = class { ... };
export function default(...) { ... }
export class default { ... }

Interesting, but whether we go the other forms or not, I'm sold on this simple desugaring of defaults. Looking good! : )