Why is "export default var a = 1;" invalid syntax?

# Glen Huang (9 years ago)

From people.mozilla.org/~jorendorff/es6-draft.html#sec-exports, it seems that export default var a = 1; is not valid in es6. I wonder why that’s the case. You can already do var a = 1;export default a;. Why not make export default var a = 1; valid?

Maybe I missed something. Could someone lighten me?

# Kevin Smith (9 years ago)

Exporting a variable declaration list as "default" wouldn't make sense: for one, the list can have more than one element:

export default var a, b, c;  // Non-sensical
# Glen Huang (9 years ago)

Oh, right. Didn’t realize it could be abused this way.

I guess there is no interest to define constructs like “LetOrConst LexicalBinding" and “var VariableDeclaration” just for this scenario?

(This was only send to Kevin by mistake, I hope you won’t mind it that I send it again)

# Glen Huang (9 years ago)

What about this:

export default var a, b, c;

is equivalent to

var a;
export default a;

export var b, c;
# Caridy Patino (9 years ago)

No.

# Erik Arvidsson (9 years ago)

Keeping syntax minimal, simple and unsurprising is part of our job.

Why do you want this?

# Glen Huang (9 years ago)

Just looking for symmetry with the "export var a" syntax.

But yeah, maybe the proposed syntax is a bit tricky, and is probably not a very good idea.

# Dave Herman (9 years ago)

On Fri, Dec 12, 2014 at 8:19 PM, Glen Huang <curvedmark at gmail.com> wrote:

You can already do var a = 1;export default a;. Why not make export default var a = 1; valid?

Because the former is creating an exported variable called 'default' and assigning its initial value to the result of evaluating an expression that happens to evaluate the current value of 'a'. There's nothing special about the fact that you used 'a' there, it's just an ordinary expression that happens to evaluate a variable.

(For historical interest, this was why I was in favor of using the equals sign in the syntax, to make it clear that export default is doing an assignment of an initializer expression to a variable, e.g.:

export default = a;

But this was unpopular and I didn't push the issue.)

At a more basic level, from a "principle of least surprise" perspective, I would have no idea what export default var a = 1; was supposed to mean.

# Marius Gundersen (9 years ago)

So are all of these legal?

var a = 1;
export a;

var b = 1;
export default b;

export var c = 1;
export var d = 1, e = 2, f = 3;

What about:

var default = 1;
export default;

var b = 1;
export default b;

Sorry for not being very good at reading EBNF

# Glen Huang (9 years ago)

Thank you for the detailed explanation. Now I have a better understand of the export declaration.

I would have no idea what export default var a = 1; was supposed to mean.

Looking back, this does seem unclear, but indulge me to share how I come up with the proposed syntax:

I was trying to export a point data structure for the mouse position. This was my initial try:

export var x = 0;
export var y = 0;
document.addEventListener(“mousemove”, (ev) => {clientX: x, clientY: y} = ev);

But then I realized in order to consume it, i need to do import * as mousePosition from …. This doesn’t look right to me, because the exported obj isn't an aggregation of independent things (the * as … syntax looks like a reminder that properties are independent), I want to use it like import mousePosition from ….

So giving the experience of you can merge export and a variable declaration into a single line, I tried to do:

export default var pt = { x: 0, y: 0 };
document.addEventListener(“mousemove”, (ev) => {clientX: pt.x, clientY: pt.y} = ev);
// hope i got the destructuring assignment right

Thus this proposal.

So obviously “merging” concept doesn’t apply when default is involved, and I now know why. Thanks again.

# Dave Herman (9 years ago)

On Mon, Dec 15, 2014 at 8:01 AM, Marius Gundersen <gundersen at gmail.com> wrote:

Sorry for not being very good at reading EBNF

Hey, who is? ;P

# Glen Huang (9 years ago)
export a;

I think this is illegal, should be {a: a}

var default = 1;

I don’t think you can do that, default is a keyword.

export default;

without a thing to be exported as default, i believe this is illegal.

If I’m not wrong, all else are correct.

# Dave Herman (9 years ago)

On Mon, Dec 15, 2014 at 8:16 AM, Glen Huang <curvedmark at gmail.com> wrote:

So giving the experience of you can merge export and a variable declaration into a single line, I tried to do:

export default var pt = { x: 0, y: 0 };
document.addEventListener(“mousemove”, (ev) => {clientX: pt.x, clientY: pt.y} = ev);
// hope i got the destructuring assignment right

Thus this proposal.

I can see that, and thanks for the field report! Basically the summary is, any time you want a default export object (as opposed to a function or class, where you have export default class and export default function shorthands) that you also want to refer to internally in the module that defines it, you'll end up needing a two-liner:

var o = { ... };
export default o;

If you only need the properties of the object, not the object itself, one style you can use is to name all the property values with local variables but not the object itself:

function foo(...) { ... }
function bar(...) { ... }
var baz = ...;
...
export default { foo, bar, baz };

So obviously “merging” concept doesn’t apply when default is involved, and I now know why. Thanks again.

No worries. JFTR, as I alluded to above, if you're exporting a function or class, you do get the merging:

export default class Foo {
  ...
}
var x = new Foo(...);
...
# Dave Herman (9 years ago)

On Mon, Dec 15, 2014 at 8:22 AM, Glen Huang <curvedmark at gmail.com> wrote:

export a;

I think this is illegal, should be {a: a}

Oops, right you are! Got one wrong on my ES6 quiz. ;-P

# Glen Huang (9 years ago)

Thank you so much. It’s now crystal clear. :)

# Glen Huang (9 years ago)

On a second thought, I do have another question:

export var a = 1;
a++;

I believe the exported value is 2?

var a = 1;
export default a;
a++;

And this is 1?

var a = 1;
export { a };
a++;

(Not sure about this one, looks like 1)

So the question is, there is no way to bind variables with the “export default” syntax, when it is used to export an object?

export default function a() {}
a = 2;

(This should be 2, right?)

# Kevin Smith (9 years ago)
export var a = 1;
a++;

I believe the exported value is 2?

Well, the value isn't exported, the "binding" is. But yes, after the module code runs, the value of the "a" export will be 2.

var a = 1;
export default a;
a++;

And this is 1?

The value of the "default" export after the module code runs will be 1.

var a = 1;
export { a };
a++;

(Not sure about this one, looks like 1)

No - this is the same case as the first above. You are exporting the "a" binding just like above.

So the question is, there is no way to bind variables with the “export default” syntax, when it is used to export an object?

Not sure what you mean by "object" (I think you might be confused by the export {} syntax), but you can rename your exports:

var a = 1;
export { a as b };

(Then you'd have an export named "b", instead of "a".)

And you can rename it to "default" if you like:

var a = 1;
export { a as default };
export default function a() {}
a = 2;

(This should be 2, right?)

I think the "default" binding in this case would still point to the function. I find this particular example completely baffling, to be honest.

# Brendan Eich (9 years ago)

Kevin Smith wrote:

I think the "default" binding in this case would still point to the function. I find this particular example completely baffling, to be honest.

This seems clear. As Dave said, he originally proposed an '=' in between 'default' and the expression to evaluate on the right. That design remembrance should make clear that the default export is a function expression (not function declaration) with 'a' the name only in the scope of that function (either for recursion or as a downward funarg).

The 'default' binding won't be mutated via the final 'a = 2' statement, so the default-exported value is still the result of evaluating the function a(){} expression.

# Glen Huang (9 years ago)

@Kevin @Brendan

Thanks for the detailed explanation. If I’m not wrong, the exported binding is the module record in the spec, right?

Also sorry for the confusion about the “object” concept I mentioned. By “object”, I mean anything qualifies as an "AssignmentExpression” in the spec.

And after reading both of your responses, my question actually becomes: is it correct that there is not way to rebind the value being exported via the “export default” declaration, unless you use something like "export { a as default };"

Does it also mean that:

export default function a() {}
a = 2;

The assignment "a = 2” will result in an error, since "function a() {}” is an expression, so “a” was never defined at that point?

From esthetic point of view, “a" being not bound in the previous snippet really surprises me, considering its “twin" version:

export function a() {}
a = 2;

If I understand the spec correctly, this should cause the “a” export in the binding be 2.

# Allen Wirfs-Brock (9 years ago)

On Dec 15, 2014, at 8:50 PM, Brendan Eich wrote:

This seems clear. As Dave said, he originally proposed an '=' in between 'default' and the expression to evaluate on the right. That design remembrance should make clear that the default export is a function expression (not function declaration) with 'a' the name only in the scope of that function (either for recursion or as a downward funarg).

The 'default' binding won't be mutated via the final 'a = 2' statement, so the default-exported value is still the result of evaluating the function a(){} expression.

Not quite how it actually ended up. See ecmascript#2302 for background.

export default function ...
export default function  * ...
export default class ...

all act as declaration that create a module local binding (for the name in the declaration part) that is initialized in the normal manner (hoisted for function/function*, statement order initialization for class). In addition that binding is exported using the reserved export name 'default'. Just like, an export of the same declaration without 'default', except such declaration use the same name for both the export name and the local binding name.

For export default, if the declaration is anonymous (this required some minor syntax tweaking) , a local binding is still created (and initialized in the manner manner) but the local binding isn't locally referencable because it doesn't have a name that can be referenced via an IdentifierReference.

If you want to export the value of a named FunctionExpression/GeneratorExpression (or ClassExpression) you need to parenthesize to force it to be an expression rather than a declaration, such as:

export default (function fact(n) {...});

Regarding Kevin's a function:

export default function a() {}; // bound name:'a' (a mutable minding), hosted initial value: function a() )), export name and association 'default'->'a'
... // any call out to a module that references the 'default' export of this module will see the value function a() {}
a=2;  //changes the value of the multiple binding 'a', export name association is still 'default'->'a'
... // any call out to a module that referenes the 'default' export of this module will see the value 2

Finally, it's import that all exports (including default) are always an association between an export name and a module local binding, never between a export name and a specific value.

# Brendan Eich (9 years ago)

Glen Huang wrote:

And after reading both of your responses, my question actually becomes: is it correct that there is not way to rebind the value being exported via the “export default” declaration, unless you use something like "export { a as default };"

I think that's right. Or by brute-force reflection on the module object, to hack the property named 'default' in it.

Does it also mean that:

export default function a() {}
a = 2;

The assignment "a = 2” will result in an error, since "function a() {}” is an expression, so “a” was never defined at that point?

In strict code, unless there was already an 'a' binding in scope, that would indeed be a runtime error. Modules are strict by definition.

From esthetic point of view, “a" being not bound in the previous snippet really surprises me, considering its “twin" version:

export function a() {}
a = 2;

Yup, wherefore Dave's original syntax design interposing an '=' before the 'function a() {}' to make clear the latter is an expression.

It's a pity the '=' isn't required. Maybe it can be an ES7 optional syntax extension, but at that point we'd regret not requiring it -- so either ES6 should be revised ASAP to require it, or we should drop the idea and learn to live with the unclear expression context.

If I understand the spec correctly, this should cause the “a” export in the binding be 2.

Yes, just like in any other JS scope, function declarations make writable bindings.

# Brendan Eich (9 years ago)

Ah, thanks -- I remember someone pointing this out, now that you mention it.

My misunderstanding in the last two posts dings my ES6 quiz score! Who will get a perfect score in a fresh (uncoached) quiz setting? Is this telling us something? Anyway, I agree that export default function a() {} making an a binding is better, it resolves the confusing aspect in my misunderstood version of ES6 that Glen cited in his 7pm post.

One more question below:

Allen Wirfs-Brock wrote:

Not quite how it actually ended up. See ecmascript#2302 for background.

export default function ...
export default function  * ...
export default class ...

all act as declaration that create a module local binding (for the name in the declaration part) that is initialized in the normal manner (hoisted for function/function*, statement order initialization for class). In addition that binding is exported using the reserved export name 'default'. Just like, an export of the same declaration without 'default', except such declaration use the same name for both the export name and the local binding name.

So assigning a = 2 won't affect the value of the default export, but will rebind a, given preceding export default function a() {} -- right?

For export default, if the declaration is anonymous (this required some minor syntax tweaking) , a local binding is still created (and initialized in the manner manner) but the local binding isn't locally referencable because it doesn't have a name that can be referenced via an IdentifierReference.

Hmm, how's that work? Throwaway symbol name?

I ask because Glen also wondered about the Module Record being reified in one of his posts. That's spec-internal, though (again, correction welcome).

If you want to export the value of a named FunctionExpression/GeneratorExpression (or ClassExpression) you need to parenthesize to force it to be an expression rather than a declaration, such as: export default (function fact(n) {...});

Right-o, this is what jogged my memory, first sentence above.

# Allen Wirfs-Brock (9 years ago)

On Dec 15, 2014, at 10:06 PM, Brendan Eich wrote:

So assigning a = 2 won't affect the value of the default export, but will rebind a, given preceding export default function a() {} -- right?

It does both. 'a' is rebound to 2 and since the export name 'default' associates to 'a', any subsequent references in other modules tht are linked to the default export of this module will also see 2 because 'default' is really just the export name for 'a'.

Hmm, how's that work? Throwaway symbol name?

Well ultimately it's all linkages that at the implementation level hopefully resolve external (ie imported) references into direct references to exported variable binding.

At the spec. level, I just define a non-identifer name ("default") to the binding created by a anonymous 'export default'. There can only be one per modules. It turns out all the declaration instantiation, binding lookup, and module linking mechanism in the spec. work fine with such a name, so there is actually very few special cases in the spec. for such anonymous default exports. See people.mozilla.org/~jorendorff/es6-draft.html#sec-exports-static-semantics-exportentries for how exports associate an export name with either a local binding name or an import name from some other module.

I ask because Glen also wondered about the Module Record being reified in one of his posts. That's spec-internal, though (again, correction welcome).

Right, Module Record is just internal spec. mechanism used to define the module semantics. It currently isn't reified.

# Glen Huang (9 years ago)

With the help of your explanation, the spec becomes much easier to read. Thanks.

Would you consider adding this:

Syntax
	export default var VariableDeclaration;

ExportEntries
Let entries be a new empty List.
Let name be the BoundNames of VariableDeclaration.
Repeat for each name in names,
Append to entries the Record {[[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]:name, [[ExportName]]: “default" }.
Return entries.
Vice versa for LexicalDeclaration.
# Glen Huang (9 years ago)

A little tweak:

Syntax
	export default var VariableDeclaration;

ExportEntries
Let names be the BoundNames of VariableDeclaration.
Let localName be the sole element of names.
Return a new List containing the Record {[[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: localName, [[ExportName]]: "default"}.
Vice versa for LexicalDeclaration.
# Kevin Smith (9 years ago)

It does both. 'a' is rebound to 2 and since the export name 'default' associates to 'a', any subsequent references in other modules tht are linked to the default export of this module will also see 2 because 'default' is really just the export name for 'a'.

Ah - got it. That makes sense.

I still agree with Dave and Brendan: it would have been more clear if the expression form export default AssignmentExpression had the "=" in there.

# Dave Herman (9 years ago)

On Mon, Dec 15, 2014 at 11:50 PM, Glen Huang <curvedmark at gmail.com> wrote:

Would you consider adding this:

Syntax export default var VariableDeclaration;

I should first say: no module syntax changes for ES6. But we can discuss extensions for post-ES6 proposals.

I was resistant to this syntax at first but I've warmed to it. In particular, it has a consistent syntax, and can be given a consistent semantics, to export default function and export default class. IOW, it creates a local binding that is also exported, but renamed to default. This does feel like a missing case on top of those other "locally named default export" forms.

Note that if we add export default var we also have to add export default let and export default const.

I think this is worth proposing, folded in with other proposed post-ES6 extensions to the module syntax (in particular the module metadata syntax). I'll be happy to do this.

Thanks for your input!

# Allen Wirfs-Brock (9 years ago)

On Dec 16, 2014, at 12:07 AM, Glen Huang wrote:

A little tweak:

Syntax
	export default var VariableDeclaration;

ExportEntries
Let names be the BoundNames of VariableDeclaration.
Let localName be the sole element of names.
Return a new List containing the Record {[[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: localName, [[ExportName]]: "default"}

Still not enough, you'd also have to make sure that things like the following are rejected as syntax errors:

export default var {a,b,c} = obj;  //their can only be one binding per module that is exported as 'default'

Regardless, ES6 is now frozen except for significant bug fixes. Extensions like this can be considered for future for subsequent editions.

BTW, I should have also pointed you to people.mozilla.org/~jorendorff/es6-draft.html#sec-static-and-runtme-semantics-module-records and in particular, the last table in that section.

# Glen Huang (9 years ago)

@Dave @Allen

Thanks for the support and thanks for making the spec more clear to me (that table example is priceless, i noticed it before, but didn’t understand it quite well :). Would be great if you could consider this syntax for the next version (and sorry for the incorrect version that I proposed).

Just to provide some use case:

I think the only time that you are very likely to do export default AssignmentExpression is to expose a default polyfill:

var setImmediate;
if (window.setImmediate) {
	setImmediate = window.setImmediate;
} else {
	setImmediate = callback => setTimeout(callback, 0);
}
export default setImmediate;

And the gotcha, like others have pointed out, is that without the “=“ syntax proposed by Dave, it’s not very clear that the exported local variable is unbound.

But if this is allowed:

export default var setImmediate;
if (window.setImmediate) {
	setImmediate = window.setImmediate;
} else {
	setImmediate = callback => setTimeout(callback, 0);
}

Then there is no gotcha, and like Dave said, it’s very consist with existing vanilla export syntax: you get to decide what is exported before you rebind it to another value (it can be done with var setImmediate; export {setImmediate as default}, but you no longer has a nice one-liner.)

My vice versa part in the previous post is trying to cover export default let and export default const. :)

# Jesse McCarthy (9 years ago)

Re: esdiscuss.org/topic/why-is-export-default-var-a-1-invalid-syntax

I find myself wanting to do this kind of thing with a function returned from a function, e.g. when using Backbone, and it seems silly that I can't:

export default var Klass = Backbone.Model.extend();
Klass.prototype.whatever = whatever;
// ...

For that use case will the following be functionally identical? Any gotchas with circular dependencies or anything?

A)

var Klass = Backbone.Model.extend();
Klass.prototype.whatever = whatever;
export default Klass;

B)

var Klass = Backbone.Model.extend();
Klass.prototype.whatever = whatever;
export { Klass as default };

C)

var Klass = Backbone.Model.extend();
export default Klass;
Klass.prototype.whatever = whatever;

D)

var Klass = Backbone.Model.extend();
export { Klass as default };
Klass.prototype.whatever = whatever;

If I was willing to use class syntax could I do this?

export default class Klass extends Backbone.Model.extend();
Klass.prototype.whatever = whatever;

Glen Huang said:

I think this is illegal, should be {a: a}

Sorry, I'm probably missing something obvious, but what is this referring to?

# caridy (9 years ago)

Jesse, you can do:

export default class Foo extends Backbone {}

in case you want to reference to the exported class in the body of the module, or you can do:

export default class extends Backbone {}

# Matthew Robb (9 years ago)

I guess the take away here is that the default export is NOT a shared binding, correct?

# Jesse McCarthy (9 years ago)

Jesse, you can do: export default class Foo extends Backbone {}

Ok, thanks. The empty block is required? The part after extends is just an expression that evaluates to a function, right? So I could do this right (where Backbone.Model.extend() returns a function)?:

export default class Klass extends Backbone.Model.extend() {}
Klass.prototype.whatever = whatever;
# caridy (9 years ago)

that’s part of the class declaration, like in:

export default class Foo extends Backbone {
      constructor() {
           …
      }
      ...
}
# Jesse McCarthy (9 years ago)

I understand. My question is if I'm not putting anything in the block, I still have to include the empty block?

# Rick Waldron (9 years ago)

On Wed Feb 18 2015 at 4:40:34 PM Jesse McCarthy <es-discuss-2015-02 at jessemccarthy.net> wrote:

Ok, thanks. The empty block is required?

Yes, just like:

function Foo() {}

Where the braces are the syntactic boundary around the FunctionBody; the braces here:

class Foo {}

...Are the syntactic boundary around the ClassBody. ClassBody has "optional" (present or not) semantics, defined at people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-classdefinitionevaluation Steps 8 & 9

# Jesse McCarthy (9 years ago)

Got it, thanks Rick.

# Bucaran (9 years ago)

Read as much as I could, but it was getting too long, so just to quickly summarize:

While you can’t:

export default let a = 1

You certainly can:

let a = 1
export default a

Or

let a = 1
let b = 2
let c = 3

export default {
 A: a,
 B: b,
 C: c
}

Cheers

# Glen Huang (9 years ago)

The point is to keep the binding:

export default let a = 1;
a = 2;

exports value 2

let a = 1
export default a;
a = 2;

exports value 1

# Glen Huang (9 years ago)

That's not enough:

let a = 1;
setTimeout(() => a = 2, 100);

export default a;

You can't export 2 in this case.

# Glen Huang (9 years ago)

This is syntax error. export is only allowed in the top level.