Why is "export default var a = 1;" invalid syntax?
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
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)
What about this:
export default var a, b, c;
is equivalent to
var a;
export default a;
export var b, c;
No.
Keeping syntax minimal, simple and unsurprising is part of our job.
Why do you want this?
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.
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 makeexport 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.
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
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.
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.
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(...);
...
Thank you so much. It’s now crystal clear. :)
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?)
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.
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.
@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.
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.
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.
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.
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 rebinda
, given precedingexport 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.
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.
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.
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.
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!
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.
@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
. :)
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?
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 {}
I guess the take away here is that the default export is NOT a shared binding, correct?
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;
that’s part of the class declaration, like in:
export default class Foo extends Backbone {
constructor() {
…
}
...
}
I understand. My question is if I'm not putting anything in the block, I still have to include the empty block?
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
Got it, thanks Rick.
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
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
That's not enough:
let a = 1;
setTimeout(() => a = 2, 100);
export default a;
You can't export 2 in this case.
This is syntax error. export is only allowed in the top level.
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 dovar a = 1;export default a;
. Why not makeexport default var a = 1;
valid?Maybe I missed something. Could someone lighten me?