Module import/export bindings
Of course in my exports, I meant export {x}
instead of export x
, but I tried to edit my OP and I get "internal server error". :)
Regarding the internal server error, see esdiscuss/esdiscuss.org#83
From my current understanding (based on other threads), this module:
var foo = 42; export default foo; export foo; foo = 10;
I guess you'd intended to write export {foo as default}
instead of export default foo
, because
the latter exports the value of the expression when it gets evaluated. IOW it's the same as export default 42
.
However, I am curious if this binding is 2-way or only 1-way. What happens if I do:
import foo, * as FOO from "coolmodule"; foo = 100; FOO.default = 200; FOO.foo = 300;
All three assignments throw a TypeError exception:
- The first assignment throws a TypeError in 8.1.1.1.5 SetMutableBinding, step 6. (Import bindings are immutable - 8.1.1.5.5 CreateImportBinding, step 5.)
- The second and third assignment throw a TypeError in 6.2.3.2 PutValue, step 6.d. (Module namespace objects always return false from [[Set]] - 9.4.6.9 [[Set]].)
Regarding the internal server error
Ahh, thanks. Yeah, not only is it confusing to see the "edit" button, but especially since clicking it asks you to login to the site as if to verify your authorization to do such. :)
I guess you'd intended to write export {foo as default} instead of export default foo...
Well, no, until your reply I didn't realize there was a difference in the two! Yikes. That's… surprising.
Just to make sure I'm crystal clear… in my original example:
var foo = 42;
export default foo;
foo = 10;
That exports a binding only to 42
, not 10
. But:
var foo = 42;
export {foo as default};
foo = 10;
That exports a binding to foo
so the importer sees 10
. Correct?
All three assignments throw a TypeError exception
Thanks for the clarifications!
Would it then be appropriate to explain that conceptually the binding would otherwise indeed be 2-way, but that the immutable/read-only nature of the bindings is what prevents an outside mutation of a module's internals? That is, without such bindings (and errors), a module could be changed from the outside?
Also, are these errors runtime or static? I'm guessing from the spec text you quoted they're runtime. If so, was there a reason they couldn't be static?
I have some modules where the intentional design of the API has been for a consumer to be able to change property value(s) on the public API, and the module would behave differently corresponding to those values, almost like if they were "configuration".
I take it there's no way to do this directly on the module namespace (even with the namespace import) with ES6 modules? So basically to do that I'd have to export an object (called like config
) that holds such intentionally mutable properties?
Similarly, the (much maligned but still somewhat popular) pattern of having module "plugins" which attach themselves on top of an existing API… sounds like this pattern is also not supported?
inline
On Sun, Mar 15, 2015 at 11:42 AM, Kyle Simpson <getify at gmail.com> wrote:
Regarding the internal server error
Ahh, thanks. Yeah, not only is it confusing to see the "edit" button, but especially since clicking it asks you to login to the site as if to verify your authorization to do such. :)
I guess you'd intended to write export {foo as default} instead of export default foo...
Well, no, until your reply I didn't realize there was a difference in the two! Yikes. That's… surprising.
Just to make sure I'm crystal clear… in my original example:
var foo = 42; export default foo; foo = 10;
That exports a binding only to
42
, not10
. But:
correct
var foo = 42; export {foo as default}; foo = 10;
That exports a binding to
foo
so the importer sees10
. Correct?
correct
All three assignments throw a TypeError exception
Thanks for the clarifications!
Would it then be appropriate to explain that conceptually the binding would otherwise indeed be 2-way, but that the immutable/read-only nature of the bindings is what prevents an outside mutation of a module's internals? That is, without such bindings (and errors), a module could be changed from the outside?
that's well documented as part of the description of the namespace exotic object.
Also, are these errors runtime or static? I'm guessing from the spec text you quoted they're runtime. If so, was there a reason they couldn't be static?
named imports will throw as part of the static verification, but namespaces can only throw during runtime due to the nature of the exotic objects.
I have some modules where the intentional design of the API has been for a consumer to be able to change property value(s) on the public API, and the module would behave differently corresponding to those values, almost like if they were "configuration".
use a method to mutate internal named exports.
I take it there's no way to do this directly on the module namespace (even with the namespace import) with ES6 modules? So basically to do that I'd have to export an object (called like
config
) that holds such intentionally mutable properties?Similarly, the (much maligned but still somewhat popular) pattern of having module "plugins" which attach themselves on top of an existing API… sounds like this pattern is also not supported?
plugins are orthogonal to modules, modules CANNOT be extended, but you can plug things into objects exported from a module.
Thanks, all answers super helpful!
One last clarification:
import "foo";
This doesn't do any binding does it? AFAICT, it just downloads and runs the module (if it hasn't already)?
If that's true, what's the use-case here besides "preloading" a module performance wise?
My guess is that it’s for importing modules that expose objects via the global object (e.g., window).
However I do have a question: is it possible for a module to access the global object without relying on the host environment?
One use case is to polyfill the language's standard library. es6-shim uses a pretty ugly hack (paulmillr/es6-shim/blob/e17ca7ad73528261a3fc4af2ad71ebc3c8f84c0e/es6-shim.js#L76, paulmillr/es6-shim/blob/e17ca7ad73528261a3fc4af2ad71ebc3c8f84c0e/es6-shim.js#L76).
I wonder what’s the most elegant way to do that?
correct, it is about evaluation.
yes, modules will have access to window, and any other runtime feature.
no, I don't recommend using modules to patch the runtime, features should probably be patched before attempting to load any module (maybe using FT polyfill service).
Didn’t know about the service. It looks really promising. Thank you for letting me know.
So you are saying polyfills should really be included as scripts and not modules? That makes sense.
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Kyle Simpson
Would it then be appropriate to explain that conceptually the binding would otherwise indeed be 2-way, but that the immutable/read-only nature of the bindings is what prevents an outside mutation of a module's internals? That is, without such bindings (and errors), a module could be changed from the outside?
I wouldn't really find this an appropriate explanation. That's kind of like saying "this building's 6th story would be blue, but the 5-story nature of its blueprints is what prevents you from accessing the 6th story." There just isn't any 6th story at all. (Similarly, there just isn't any defined [[Set]] behavior for module namespace objects at all. You could make up a plausible one, like pretending it would modify the original module's bindings, and write a revisionist history in which it was removed. But that's not really how the spec works.)
On second thought, this does seem to imply that polyfills can’t use the module syntax, which means they can’t use utility libraries written in module syntax, and, if you are writing a complex polyfill, managing dependencies requires ensuring correct script loading order (whether that means managing script element order or file concatenating order in a build step). Not to mention having to wrap each script in an IIFE.
This feels like being thrown back into the dark days, and I don’t think writing polyfills should be that different.
So now I have more questions:
- When the environment you are trying to polyfill exposes the global object, should you use the module syntax?
- When polyfilling the language’s standard library, should you use the module syntax? If so, how do you get hold of the global object?
- If the answers to these questions are no, how do you use a library, written in module syntax, in the polyfill that can later be reuse by the project (without converting the library to script syntax and duplicate the code in the end of cause)?
On Mar 15, 2015, at 9:43 PM, Domenic Denicola wrote:
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Kyle Simpson
Would it then be appropriate to explain that conceptually the binding would otherwise indeed be 2-way, but that the immutable/read-only nature of the bindings is what prevents an outside mutation of a module's internals? That is, without such bindings (and errors), a module could be changed from the outside?
I wouldn't really find this an appropriate explanation. That's kind of like saying "this building's 6th story would be blue, but the 5-story nature of its blueprints is what prevents you from accessing the 6th story." There just isn't any 6th story at all. (Similarly, there just isn't any defined [[Set]] behavior for module namespace objects at all. You could make up a plausible one, like pretending it would modify the original module's bindings, and write a revisionist history in which it was removed. But that's not really how the spec works.)
the simple story:
imported bindings are all const
bindings. Think of them as if if they were written
const import {a,b,c} from "foo";
Story is way too simple. JS const means constant, unchanging. By contrast, import bindings, like const in C++, means "read-only view". This is very different from constant.
Don't use the "const" analogy when changes are still observable.
On Mar 16, 2015, at 9:26 AM, Mark Miller wrote:
Story is way too simple. JS const means constant, unchanging. By contrast, import bindings, like const in C++, means "read-only view". This is very different from constant.
Don't use the "const" analogy when changes are still observable.
got me...not enough morning coffee yet...
Yes, they are like const
in that they can't be assigned to. They are unlike const
in that in some cases their observable values may change.
inline
On Mar 16, 2015, at 2:21 AM, Glen Huang <curvedmark at gmail.com> wrote:
On second thought, this does seem to imply that polyfills can’t use the module syntax, which means they can’t use utility libraries written in module syntax, and, if you are writing a complex polyfill, managing dependencies requires ensuring correct script loading order (whether that means managing script element order or file concatenating order in a build step). Not to mention having to wrap each script in an IIFE.
Glen, I said "I don't recommend it" (as my personal opinion), I never said it can't be done. The reality is that we don't know yet how are we going to introduce new features after modules become ubiquitous, maybe new features will come in the form of modules that you have to require, and to polyfill new features you just need a synthetic module that exports the right API, we just don't know yet. But polyfills, in their current state, are simply runtime patches, and using them should NOT require a module system as today, they are effectible scripts. That does not means you can't write polyfills using ES6 syntax, you can, just transpile them to scripts :)
Thank you for showing me the bigger picture.
If there ever come a day when web/stdlib APIs are exposed as modules, then polyfills are themselves modules.
But if modules aren’t supposed to patch the runtime as effectible scripts do, then I have the same question as Kyle does, what’s the use case for importing a module without binding it to an identifier?
If you assign another variable from an imported binding, is that assignment done as a "reference to the binding" (aka creating another binding) or via normal reference-copy/value-copy assignment behavior?
export var a = 42;
export function b() { console.log("orig"); };
export function change() {
a = 100;
b = function b() { console.log("new"); };
};
And then:
import { a, b, change } from "...";
var x = a, y = b;
x; // 42
y(); // "orig"
change();
a; // 100
b(); // "new"
x; // ??
y(); // ??
Will the final x
and y()
result in:
42
/"orig"
42
/"new"
100
/"orig"
100
/"new"
Kyle, we are NOT changing the semantic of the assignment expression. Your example has nothing to do with import/export ;)
we are NOT changing the semantic of the assignment expression.
So the result is going to be 42
/ "orig"
, right? :)
The reason I asked is not because I thought we were changing the semantic of the assignment expression, but because I wasn't sure if this top-level "const or whatever binding" was some sort of special thing that you can only hold a reference to. Clearly not.
Also, since y
keeps a reference to the "orig"inal function reference imported, even if the module updates itself, this very well may affect those who (in places other than this thread, and for different reasons) have often suggested they plan to do stuff like:
import { y } from "..";
let x = y;
..
// use x now
In those cases, I was trying to find out if x
could be updated by the module itself, like y
can, because that matters (either good or bad) to the desire to use such a pattern. Since x
can't get updated here, it's now clear to me that I wouldn't want to use such pattern, for fear that I am not using the latest API binding.
Thanks for clarifications!
Kyle, same answer, let x = y
is just an assignment. btw, you cannot change y
in that module, that's a static error.
I have not been following this thread, so my response may be out of context. But I have to point out that
let x = y;
is not an assignment. It is an initialization.
ah, right, I was thinking about x = y
. In any case, the same applies, import and export declarations will not break the mental model for assignments, initializations, etc, the result for the original example will be 42
/ "orig"
.
On Mar 18, 2015, at 9:34 AM, caridy wrote:
Kyle, same answer,
let x = y
is just an assignment. btw, you cannot changey
in that module, that's a static error.
Assignment to 'y' a dynamic error. A while ago we eliminated all early errors that required static determination that their target was going to be an immutable binding.
Allen Wirfs-Brock wrote:
On Mar 18, 2015, at 9:34 AM, caridy wrote:
Kyle, same answer,
let x = y
is just an assignment. btw, you cannot changey
in that module, that's a static error.Assignment to 'y' a dynamic error. A while ago we eliminated all early errors that required static determination that their target was going to be an immutable binding.
Right. Import bindings are like getter-only accessors. Dynamic error to assign to them.
From my current understanding (based on other threads), this module:
var foo = 42; export default foo; export foo; foo = 10;
When imported:
import foo, * as FOO from "coolmodule"; foo; // 10 FOO.default; // 10 FOO.foo; // 10
However, I am curious if this binding is 2-way or only 1-way. What happens if I do:
import foo, * as FOO from "coolmodule"; foo = 100; FOO.default = 200; FOO.foo = 300;
Have I changed the local
foo
variable inside the module? If so, to which value?Moreover, what are now the values of these three in my imported context:
foo; // ?? FOO.default; // ?? FOO.foo; // ??
Have they all become 300? or are they 100, 200, and 300 separately?