Export Default Sugarings

# Kevin Smith (12 years ago)

TLDR: We should consider deferring specialized syntax for exporting a "default" binding until after ES6.

In the current draft, there are 3 ways to export a default binding:

  1. Export a local binding with the name "default":

    class Parser { ... }
    export { Parser as default };
    
  2. Use "default" as a BindingIdentifier. BindingIdentifer is parameterized on [Default]:

    export class default { ... }
    
  3. Use a "default" assignment:

    export default class { ... }; // AssignmentExpression
    

Methods 2 and 3 are inessential - they can be expressed in terms of 1.

Methods 2 and 3 are minor optimizations. Method 1 is by no means painful to write or read.

There are some potential downsides to providing methods 2 and 3:

  • It may be that they provide too many ways to do the same thing, with too little to differentiate them.
  • Method 2 makes the grammar more complex, for parsers and (more importantly) users.
  • Method 3, being an assignment, introduces initialization-order sensitivity that otherwise might be avoidable.
  • They bind orthogonal issues: On the one hand, the need to export a default binding, and on the other, the need to reference a local binding with some local name.

Proposal: defer "default export" sugarings until ES7, when we can use data gathered from experience with ES6 modules to guide syntax additions.

# Axel Rauschmayer (12 years ago)

Good observation. However, if I were to simplify, I’d only keep #3. #1 and #2 look more syntactically questionable to me (kind of abusing names as keywords/markers).

# Kevin Smith (12 years ago)

Good observation. However, if I were to simplify, I’d only keep #3. #1 and #2 look more syntactically questionable to me (kind of abusing names as keywords/markers).

#1 is the base case because it is completely general. Any IdentifierName may appear in the "as" clause.

export { x as delete, y as new, z as default }; // Perfectly fine!

Of course, "default" has special meaning on the import side:

import x from "package:foo";

Desugars (very beautifully) to:

import { default as x } from "package:foo";

The sugaring on the import side is a clear win, but I'm not so sure about the sugarings on the export side.

# Erik Arvidsson (12 years ago)

#2 was removed at the hallway discussion of the face to face meeting. Do you still think we should remove #3?

# Kevin Smith (12 years ago)

My only real issue with #3 is that it raises a footgun flag for me:

export default function parse1JS(code) {
    // ...
}

parse1JS.foo = "bar"; // Surprise! [ReferenceError: parse1JS" is not defined]

It's an assignment, but it looks like a declaration. Being an assignment, you need the "=" to signal to the user that what follows is an expression:

export default = function parse1JS(code) { };  // Obviously an expression!
# Andreas Rossberg (12 years ago)

On 5 December 2013 16:48, Kevin Smith <zenparsing at gmail.com> wrote:

#1 is the base case because it is completely general. Any IdentifierName may appear in the "as" clause.

export { x as delete, y as new, z as default }; // Perfectly fine!
```

I certainly wouldn't mind this simplification either.

Of course, "default" has special meaning on the import side:

import x from "package:foo";

Desugars (very beautifully) to:

```js
import { default as x } from "package:foo";

The sugaring on the import side is a clear win, but I'm not so sure about the sugarings on the export side.

Although the import side has this one extra syntactic special case for defaults:

import x, {y as z, ...} from "..."

That seems really redundant. I assume mixing default import with others will be an extremely rare case, and when really needed, can easily be expressed as either

import {default as x, y as z, ...} from "..."

or

import x from "..."
import {y as z, ...} from "..."
# Kevin Smith (12 years ago)

I didn't even realize that was allowed, and I agree with you completely.

# Erik Arvidsson (12 years ago)

The import crazy, {more as boo} from 'name' needs to go away.

ecmascript#2287

It is also bad because the order there is fixed and you cannot interleave.

# Kevin Smith (12 years ago)

#2 was removed at the hallway discussion of the face to face meeting. Do you still think we should remove #3?

After thinking about it a little more, I think deferring #3 might be best after all. Let's assume that the syntax is export default = AssignmentExpression. And further, let's assume that when a default export is specified, it will almost invariably be a function or class. If so, then #3 will almost always be inferior to #1.

Consider the following module cycle, where the modules are arranged in their execution order:

// A.js
import b from "B.js";
b(); // Error: b has not been initialized

// B.js
import {} from "A.js";
function b() {}
export default = b;

// main.js
import {} from "B.js";

I believe that this will fail, because the local binding "b" in "A.js" has not been initialized yet. However, using #1 it works just fine:

// A.js
import b from "B.js";
b(); // OK

// B.js
import {} from "A.js";
function b() {}
export { b as default };

// main.js
import {} from "B.js";

The aesthetic advantage of #3 over #1 is arguable at best, so it appears that the only way in which #3 is justified is when the exported value is actually constructed at runtime. At this time, it's not clear to me that this is anything but a rare edge case. If ES6 usage patterns prove otherwise, then a case can be built for adding syntax in ES7.

Thanks for hearing me out!