How to solve this basic ES6-module circular dependency problem?
Oops, I forgot the export in module C:
// --- Module C
import A from './A'
import B from './B'
console.log('Module C', A, B)
class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
export {C as default} // export!
Oops, I forgot the export in module C: ```js // --- Module C import A from './A' import B from './B' console.log('Module C', A, B) class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } export {C as default} // export! ``` */#!/*JoePea On Tue, Aug 9, 2016 at 1:03 PM, /#!/JoePea <joe at trusktr.io> wrote: > I have the very basic problem detailed at http://stackoverflow.com/ > questions/38841469, but thought I'd bring it up here because it makes me > wonder about the ES6 Module system. > > Why is it that the body of module C is not evaluated before the bodies of > modules A and B? I'm just wondering, because if that were the case, then it > would work and the entrypoint would eventually be evaluated without errors. > > A possibility could be that maybe it *should* work, and that my ES6 module > environment simply doesn't handle it as it should? (But I'm not familiar > enough with the spec, so that's why I'm not sure about that.) > > These are the modules, posted here for convenience: > > ```js > // --- Entrypoint > > import A from './app/A' > console.log('Entrypoint', A) > ``` > > ```js > // --- Module A > > import C from './C' > > console.log('Module A', C) > > class A extends C { > // ... > } > > export {A as default} > ``` > > ```js > // --- Module B > > import C from './C' > > console.log('Module B', C) > > class B extends C { > // ... > } > > export {B as default} > ``` > > ```js > // --- Module C > > import A from './A' > import B from './B' > > console.log('Module C', A, B) > > class C { > constructor() { > // this may run later, after all three modules are evaluated, or > // possibly never. > console.log(A) > console.log(B) > } > } > ``` > > */#!/*JoePea > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160809/3639935a/attachment-0001.html>
For reference, I'm trying this in Meteor, in which case the first module to
be evaluated is module B, but that means that C
is undefined
during
evaluation of B
, so we get an error.
For reference, I'm trying this in Meteor, in which case the first module to be evaluated is module B, but that means that `C` is `undefined` during evaluation of `B`, so we get an error. */#!/*JoePea On Tue, Aug 9, 2016 at 1:05 PM, /#!/JoePea <joe at trusktr.io> wrote: > Oops, I forgot the export in module C: > > ```js > // --- Module C > > import A from './A' > import B from './B' > > console.log('Module C', A, B) > > class C { > constructor() { > // this may run later, after all three modules are evaluated, or > // possibly never. > console.log(A) > console.log(B) > } > } > > export {C as default} // export! > ``` > > */#!/*JoePea > > On Tue, Aug 9, 2016 at 1:03 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> I have the very basic problem detailed at http://stackoverflow.com/qu >> estions/38841469, but thought I'd bring it up here because it makes me >> wonder about the ES6 Module system. >> >> Why is it that the body of module C is not evaluated before the bodies of >> modules A and B? I'm just wondering, because if that were the case, then it >> would work and the entrypoint would eventually be evaluated without errors. >> >> A possibility could be that maybe it *should* work, and that my ES6 >> module environment simply doesn't handle it as it should? (But I'm not >> familiar enough with the spec, so that's why I'm not sure about that.) >> >> These are the modules, posted here for convenience: >> >> ```js >> // --- Entrypoint >> >> import A from './app/A' >> console.log('Entrypoint', A) >> ``` >> >> ```js >> // --- Module A >> >> import C from './C' >> >> console.log('Module A', C) >> >> class A extends C { >> // ... >> } >> >> export {A as default} >> ``` >> >> ```js >> // --- Module B >> >> import C from './C' >> >> console.log('Module B', C) >> >> class B extends C { >> // ... >> } >> >> export {B as default} >> ``` >> >> ```js >> // --- Module C >> >> import A from './A' >> import B from './B' >> >> console.log('Module C', A, B) >> >> class C { >> constructor() { >> // this may run later, after all three modules are evaluated, or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> ``` >> >> */#!/*JoePea >> > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160809/0f04b6f9/attachment.html>
On Tue, Aug 9, 2016 at 3:03 PM, /#!/JoePea <joe at trusktr.io> wrote:
Why is it that the body of module C is not evaluated before the bodies of modules A and B?
The module system does execute all a module's dependencies before executing that module, except in cases like this where there are cycles in the dependency graph and that requirement is therefore impossible to satisfy for all modules simultaneously.
On Tue, Aug 9, 2016 at 3:03 PM, /#!/JoePea <joe at trusktr.io> wrote: > Why is it that the body of module C is not evaluated before the bodies of > modules A and B? > The module system *does* execute all a module's dependencies before executing that module, *except* in cases like this where there are cycles in the dependency graph and that requirement is therefore impossible to satisfy for all modules simultaneously. -j -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160809/3b35dfde/attachment.html>
True, and so that's why I'm wondering if the module system can see that it can satisfy all module requirements if it simply evaluates module C first, followed by A or B in any order. It is easy for us humans to see that. It would be nice for the module system to see that as well (I'm not sure if that is spec'd or not).
Hi Jason, True, and so that's why I'm wondering if the module system can see that it can satisfy all module requirements if it simply evaluates module C first, followed by A or B in any order. It is easy for us humans to see that. It would be nice for the module system to see that as well (I'm not sure if that is spec'd or not). - Joe */#!/*JoePea On Tue, Aug 9, 2016 at 2:04 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote: > On Tue, Aug 9, 2016 at 3:03 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> Why is it that the body of module C is not evaluated before the bodies of >> modules A and B? >> > > The module system *does* execute all a module's dependencies before > executing that module, *except* in cases like this where there are cycles > in the dependency graph and that requirement is therefore impossible to > satisfy for all modules simultaneously. > > -j > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160809/9c9b5d70/attachment.html>
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote:
True, and so that's why I'm wondering if the module system can see that it can satisfy all module requirements if it simply evaluates module C first, followed by A or B in any order. It is easy for us humans to see that. It would be nice for the module system to see that as well (I'm not sure if that is spec'd or not).
That knowledge requires, at minimum, evaluating the rest of each
module, beyond what is expressed in the import
statements. That's
assuming there's no dynamic trickery going on that would invalidate
whatever assumptions it can draw from surface-level analysis.
Because of this, only the import
statements are declaratively
available to the module system to work with. Based on that, it
definitely can't make any ordering assumptions; all it knows is that A
imports C, B imports C, and C imports both A and B, making a circular
import.
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: > True, and so that's why I'm wondering if the module system can see that it > can satisfy all module requirements if it simply evaluates module C first, > followed by A or B in any order. It is easy for us humans to see that. It > would be nice for the module system to see that as well (I'm not sure if > that is spec'd or not). That knowledge requires, at minimum, evaluating the rest of each module, beyond what is expressed in the `import` statements. That's assuming there's no dynamic trickery going on that would invalidate whatever assumptions it can draw from surface-level analysis. Because of this, only the `import` statements are declaratively available to the module system to work with. Based on that, it definitely can't make any ordering assumptions; all it knows is that A imports C, B imports C, and C imports both A and B, making a circular import. ~TJ
Without a way to load "later" (aka "soft") dependencies, ES6 module will continue to be more or less broken for circular dependencies.
Without a way to load "later" (aka "soft") dependencies, ES6 module will continue to be more or less broken for circular dependencies. On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote: > On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: > > True, and so that's why I'm wondering if the module system can see that > it > > can satisfy all module requirements if it simply evaluates module C > first, > > followed by A or B in any order. It is easy for us humans to see that. It > > would be nice for the module system to see that as well (I'm not sure if > > that is spec'd or not). > > That knowledge requires, at minimum, evaluating the rest of each > module, beyond what is expressed in the `import` statements. That's > assuming there's no dynamic trickery going on that would invalidate > whatever assumptions it can draw from surface-level analysis. > > Because of this, only the `import` statements are declaratively > available to the module system to work with. Based on that, it > definitely can't make any ordering assumptions; all it knows is that A > imports C, B imports C, and C imports both A and B, making a circular > import. > > ~TJ > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160809/a9c0c2de/attachment.html>
It seems that the environment I'm in (Meteor uses reify) tries to evaluate A and B first, so I thought I could take advantage of "live bindings" by changing my modules to the following:
// --- Entrypoint
import A from './app/A'
console.log('Entrypoint', A)
// --- Module A
import C from './C'
let A
export
function setUpA() {
console.log('setUpA')
console.log(C)
A = class A extends C {
// ...
}
}
console.log('Module A', C, setUpA)
export {A as default}
// --- Module B
import C from './C'
let B
export
function setUpB() {
console.log('setUpB', C)
B = class B extends C {
// ...
}
}
console.log('Module B', C, setUpB)
export {B as default}
// --- Module C
import A, {setUpA} from './A'
import B, {setUpB} from './B'
let C = class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
setUpA()
console.log('Module C', A)
setUpB()
console.log('Module C', B)
export {C as default}
As you can see, modules A and B simply export the code that should be
evaluated (note the live bindings). Then finally, the C module is evaluated
last. At the end of the C module, you see that it calls setUpA
and
setUpB
. When it fires setUpA
, an error is thrown on the second
console.log
that C
is undefined (or, specifically, C.default
is
undefined
because the ES6 modules are compiled into CommonJS form).
I thought that if C
was a live binding, then it should be ready by the
time the setUpA
function is called. Should this in fact be the case?
It seems that the environment I'm in (Meteor uses [reify]( https://github.com/benjamn/reify)) tries to evaluate A and B first, so I thought I could take advantage of "live bindings" by changing my modules to the following: ```js // --- Entrypoint import A from './app/A' console.log('Entrypoint', A) ``` ```js // --- Module A import C from './C' let A export function setUpA() { console.log('setUpA') console.log(C) A = class A extends C { // ... } } console.log('Module A', C, setUpA) export {A as default} ``` ```js // --- Module B import C from './C' let B export function setUpB() { console.log('setUpB', C) B = class B extends C { // ... } } console.log('Module B', C, setUpB) export {B as default} ``` ```js // --- Module C import A, {setUpA} from './A' import B, {setUpB} from './B' let C = class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } setUpA() console.log('Module C', A) setUpB() console.log('Module C', B) export {C as default} ``` As you can see, modules A and B simply export the code that should be evaluated (note the live bindings). Then finally, the C module is evaluated last. At the end of the C module, you see that it calls `setUpA` and `setUpB`. When it fires `setUpA`, an error is thrown on the second `console.log` that `C` is undefined (or, specifically, `C.default` is `undefined` because the ES6 modules are compiled into CommonJS form). I thought that if `C` was a live binding, then it should be ready by the time the `setUpA` function is called. Should this in fact be the case? */#!/*JoePea On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> wrote: > Without a way to load "later" (aka "soft") dependencies, ES6 module will > continue to be more or less broken for circular dependencies. > > On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com> > wrote: > >> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >> > True, and so that's why I'm wondering if the module system can see that >> it >> > can satisfy all module requirements if it simply evaluates module C >> first, >> > followed by A or B in any order. It is easy for us humans to see that. >> It >> > would be nice for the module system to see that as well (I'm not sure if >> > that is spec'd or not). >> >> That knowledge requires, at minimum, evaluating the rest of each >> module, beyond what is expressed in the `import` statements. That's >> assuming there's no dynamic trickery going on that would invalidate >> whatever assumptions it can draw from surface-level analysis. >> >> Because of this, only the `import` statements are declaratively >> available to the module system to work with. Based on that, it >> definitely can't make any ordering assumptions; all it knows is that A >> imports C, B imports C, and C imports both A and B, making a circular >> import. >> >> ~TJ >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160809/a9fb712f/attachment.html>
I can get the whole thing to work if I pass the C dependency into the
setUpA
and setUpB
functions as follows, but oddly A
is undefined
in
the Entrypoint module at the console.log
statement, which makes it seem
to me like live bindings aren't working the I was expecting.
// --- Entrypoint
import A from './app/A'
console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined"
// --- Module A
import C from './C'
let A
export
function setUpA(C) {
console.log('setUpA')
console.log(C)
A = class A extends C {
// ...
}
}
console.log('Module A', C, setUpA)
export {A as default}
// --- Module B
import C from './C'
let B
export
function setUpB(C) {
console.log('setUpB', C)
B = class B extends C {
// ...
}
}
console.log('Module B', C, setUpB)
export {B as default}
// --- Module C
import A, {setUpA} from './A'
import B, {setUpB} from './B'
let C = class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
setUpA(C)
console.log('Module C', A)
setUpB(C)
console.log('Module C', B)
export {C as default}
I can get the whole thing to work if I pass the C dependency into the `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in the Entrypoint module at the `console.log` statement, which makes it seem to me like live bindings aren't working the I was expecting. ```js // --- Entrypoint import A from './app/A' console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" ``` ```js // --- Module A import C from './C' let A export function setUpA(C) { console.log('setUpA') console.log(C) A = class A extends C { // ... } } console.log('Module A', C, setUpA) export {A as default} ``` ```js // --- Module B import C from './C' let B export function setUpB(C) { console.log('setUpB', C) B = class B extends C { // ... } } console.log('Module B', C, setUpB) export {B as default} ``` ```js // --- Module C import A, {setUpA} from './A' import B, {setUpB} from './B' let C = class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } setUpA(C) console.log('Module C', A) setUpB(C) console.log('Module C', B) export {C as default} ``` */#!/*JoePea On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: > It seems that the environment I'm in (Meteor uses [reify]( > https://github.com/benjamn/reify)) tries to evaluate A and B first, so I > thought I could take advantage of "live bindings" by changing my modules to > the following: > > ```js > // --- Entrypoint > > import A from './app/A' > console.log('Entrypoint', A) > ``` > > ```js > // --- Module A > > import C from './C' > > let A > > export > function setUpA() { > > console.log('setUpA') > console.log(C) > > A = class A extends C { > // ... > } > > } > > console.log('Module A', C, setUpA) > > export {A as default} > ``` > > ```js > // --- Module B > > import C from './C' > > let B > > export > function setUpB() { > > console.log('setUpB', C) > > B = class B extends C { > // ... > } > > } > > console.log('Module B', C, setUpB) > > export {B as default} > ``` > > ```js > // --- Module C > > import A, {setUpA} from './A' > import B, {setUpB} from './B' > > let C = class C { > constructor() { > // this may run later, after all three modules are evaluated, or > // possibly never. > console.log(A) > console.log(B) > } > } > > setUpA() > console.log('Module C', A) > > setUpB() > console.log('Module C', B) > > export {C as default} > ``` > > As you can see, modules A and B simply export the code that should be > evaluated (note the live bindings). Then finally, the C module is evaluated > last. At the end of the C module, you see that it calls `setUpA` and > `setUpB`. When it fires `setUpA`, an error is thrown on the second > `console.log` that `C` is undefined (or, specifically, `C.default` is > `undefined` because the ES6 modules are compiled into CommonJS form). > > I thought that if `C` was a live binding, then it should be ready by the > time the `setUpA` function is called. Should this in fact be the case? > > */#!/*JoePea > > On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> wrote: > >> Without a way to load "later" (aka "soft") dependencies, ES6 module will >> continue to be more or less broken for circular dependencies. >> >> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com> >> wrote: >> >>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> > True, and so that's why I'm wondering if the module system can see >>> that it >>> > can satisfy all module requirements if it simply evaluates module C >>> first, >>> > followed by A or B in any order. It is easy for us humans to see that. >>> It >>> > would be nice for the module system to see that as well (I'm not sure >>> if >>> > that is spec'd or not). >>> >>> That knowledge requires, at minimum, evaluating the rest of each >>> module, beyond what is expressed in the `import` statements. That's >>> assuming there's no dynamic trickery going on that would invalidate >>> whatever assumptions it can draw from surface-level analysis. >>> >>> Because of this, only the `import` statements are declaratively >>> available to the module system to work with. Based on that, it >>> definitely can't make any ordering assumptions; all it knows is that A >>> imports C, B imports C, and C imports both A and B, making a circular >>> import. >>> >>> ~TJ >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160809/3b149e5a/attachment-0001.html>
When I try this same code with Webpack, I get the exact same results: the
console.log
statements in the exact same order, where the last output
shows that A
in the entry point is undefined
).
Am I misunderstanding something about live bindings? Is there some guaranteed order in which these modules should be evaluated?
The reason why I'm after a solution for the circular dependency is because
in my real-world case I need to use instanceof A
and intanceof B
within
the C
superclass defined in module C. This is a case of the Fragile Base
Class Problem where a class should usually not have knowledge of it's
subclasses, but the base class in my case is intended to be internal only,
not a part of the public API that end users will extend from.
When I try this same code with Webpack, I get the *exact same results*: the `console.log` statements in the exact same order, where the last output shows that `A` in the entry point is `undefined`). Am I misunderstanding something about live bindings? Is there some guaranteed order in which these modules should be evaluated? The reason why I'm after a solution for the circular dependency is because in my real-world case I need to use `instanceof A` and `intanceof B` within the `C` superclass defined in module C. This is a case of the Fragile Base Class Problem where a class should usually *not* have knowledge of it's subclasses, but the base class in my case is intended to be internal only, not a part of the public API that end users will extend from. */#!/*JoePea On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: > I can get the whole thing to work if I pass the C dependency into the > `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in > the Entrypoint module at the `console.log` statement, which makes it seem > to me like live bindings aren't working the I was expecting. > > ```js > // --- Entrypoint > > import A from './app/A' > console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" > ``` > > ```js > // --- Module A > > import C from './C' > > let A > > export > function setUpA(C) { > > console.log('setUpA') > console.log(C) > > A = class A extends C { > // ... > } > > } > > console.log('Module A', C, setUpA) > > export {A as default} > ``` > > ```js > // --- Module B > > import C from './C' > > let B > > export > function setUpB(C) { > > console.log('setUpB', C) > > B = class B extends C { > // ... > } > > } > > console.log('Module B', C, setUpB) > > export {B as default} > ``` > > ```js > // --- Module C > > import A, {setUpA} from './A' > import B, {setUpB} from './B' > > let C = class C { > constructor() { > // this may run later, after all three modules are evaluated, or > // possibly never. > console.log(A) > console.log(B) > } > } > > setUpA(C) > console.log('Module C', A) > > setUpB(C) > console.log('Module C', B) > > export {C as default} > ``` > > */#!/*JoePea > > On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> It seems that the environment I'm in (Meteor uses [reify]( >> https://github.com/benjamn/reify)) tries to evaluate A and B first, so I >> thought I could take advantage of "live bindings" by changing my modules to >> the following: >> >> ```js >> // --- Entrypoint >> >> import A from './app/A' >> console.log('Entrypoint', A) >> ``` >> >> ```js >> // --- Module A >> >> import C from './C' >> >> let A >> >> export >> function setUpA() { >> >> console.log('setUpA') >> console.log(C) >> >> A = class A extends C { >> // ... >> } >> >> } >> >> console.log('Module A', C, setUpA) >> >> export {A as default} >> ``` >> >> ```js >> // --- Module B >> >> import C from './C' >> >> let B >> >> export >> function setUpB() { >> >> console.log('setUpB', C) >> >> B = class B extends C { >> // ... >> } >> >> } >> >> console.log('Module B', C, setUpB) >> >> export {B as default} >> ``` >> >> ```js >> // --- Module C >> >> import A, {setUpA} from './A' >> import B, {setUpB} from './B' >> >> let C = class C { >> constructor() { >> // this may run later, after all three modules are evaluated, or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> >> setUpA() >> console.log('Module C', A) >> >> setUpB() >> console.log('Module C', B) >> >> export {C as default} >> ``` >> >> As you can see, modules A and B simply export the code that should be >> evaluated (note the live bindings). Then finally, the C module is evaluated >> last. At the end of the C module, you see that it calls `setUpA` and >> `setUpB`. When it fires `setUpA`, an error is thrown on the second >> `console.log` that `C` is undefined (or, specifically, `C.default` is >> `undefined` because the ES6 modules are compiled into CommonJS form). >> >> I thought that if `C` was a live binding, then it should be ready by the >> time the `setUpA` function is called. Should this in fact be the case? >> >> */#!/*JoePea >> >> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> wrote: >> >>> Without a way to load "later" (aka "soft") dependencies, ES6 module will >>> continue to be more or less broken for circular dependencies. >>> >>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com> >>> wrote: >>> >>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> > True, and so that's why I'm wondering if the module system can see >>>> that it >>>> > can satisfy all module requirements if it simply evaluates module C >>>> first, >>>> > followed by A or B in any order. It is easy for us humans to see >>>> that. It >>>> > would be nice for the module system to see that as well (I'm not sure >>>> if >>>> > that is spec'd or not). >>>> >>>> That knowledge requires, at minimum, evaluating the rest of each >>>> module, beyond what is expressed in the `import` statements. That's >>>> assuming there's no dynamic trickery going on that would invalidate >>>> whatever assumptions it can draw from surface-level analysis. >>>> >>>> Because of this, only the `import` statements are declaratively >>>> available to the module system to work with. Based on that, it >>>> definitely can't make any ordering assumptions; all it knows is that A >>>> imports C, B imports C, and C imports both A and B, making a circular >>>> import. >>>> >>>> ~TJ >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160809/6999f0e8/attachment-0001.html>
First of all, I'll point out that even if it's an internal API, you should
just initialize them immediately. You already have an otherwise fully
initialized C, so you should just add them whenever it comes. You shouldn't
need a setUpA
export, especially called by one of its dependencies. Just
declare and initialize that crap when it's being declared.
/* index.js */
import A from './app/A'
console.log('Entrypoint', A)
/* app/A.js */
import C from './C'
export default class A eclxtends C {
// ...
}
// set up A here
console.log('Module A')
/* app/B.js */
import C from './C'
export default class B extends A {
// ...
}
// set up B here
console.log('Module B')
/* app/C.js */
import A from './A'
import B from './B'
export default class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
// set up C
console.log('Module C')
What's your full output, anyways? That would help me best explain what's going on, though.
First of all, I'll point out that even if it's an internal API, you should just initialize them immediately. You already have an otherwise fully initialized C, so you should just add them whenever it comes. You shouldn't need a `setUpA` export, especially called by one of its dependencies. Just declare and initialize that crap when it's being declared. ```js /* index.js */ import A from './app/A' console.log('Entrypoint', A) ``` ```js /* app/A.js */ import C from './C' export default class A eclxtends C { // ... } // set up A here console.log('Module A') ``` ```js /* app/B.js */ import C from './C' export default class B extends A { // ... } // set up B here console.log('Module B') ``` ```js /* app/C.js */ import A from './A' import B from './B' export default class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } // set up C console.log('Module C') ``` What's your full output, anyways? That would help me best explain what's going on, though. On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: > When I try this same code with Webpack, I get the *exact same results*: > the `console.log` statements in the exact same order, where the last output > shows that `A` in the entry point is `undefined`). > > Am I misunderstanding something about live bindings? Is there some > guaranteed order in which these modules should be evaluated? > > The reason why I'm after a solution for the circular dependency is because > in my real-world case I need to use `instanceof A` and `intanceof B` within > the `C` superclass defined in module C. This is a case of the Fragile Base > Class Problem where a class should usually *not* have knowledge of it's > subclasses, but the base class in my case is intended to be internal only, > not a part of the public API that end users will extend from. > > */#!/*JoePea > > On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> I can get the whole thing to work if I pass the C dependency into the >> `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in >> the Entrypoint module at the `console.log` statement, which makes it seem >> to me like live bindings aren't working the I was expecting. >> >> ```js >> // --- Entrypoint >> >> import A from './app/A' >> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >> ``` >> >> ```js >> // --- Module A >> >> import C from './C' >> >> let A >> >> export >> function setUpA(C) { >> >> console.log('setUpA') >> console.log(C) >> >> A = class A extends C { >> // ... >> } >> >> } >> >> console.log('Module A', C, setUpA) >> >> export {A as default} >> ``` >> >> ```js >> // --- Module B >> >> import C from './C' >> >> let B >> >> export >> function setUpB(C) { >> >> console.log('setUpB', C) >> >> B = class B extends C { >> // ... >> } >> >> } >> >> console.log('Module B', C, setUpB) >> >> export {B as default} >> ``` >> >> ```js >> // --- Module C >> >> import A, {setUpA} from './A' >> import B, {setUpB} from './B' >> >> let C = class C { >> constructor() { >> // this may run later, after all three modules are evaluated, or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> >> setUpA(C) >> console.log('Module C', A) >> >> setUpB(C) >> console.log('Module C', B) >> >> export {C as default} >> ``` >> >> */#!/*JoePea >> >> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> It seems that the environment I'm in (Meteor uses [reify]( >>> https://github.com/benjamn/reify)) tries to evaluate A and B first, so >>> I thought I could take advantage of "live bindings" by changing my modules >>> to the following: >>> >>> ```js >>> // --- Entrypoint >>> >>> import A from './app/A' >>> console.log('Entrypoint', A) >>> ``` >>> >>> ```js >>> // --- Module A >>> >>> import C from './C' >>> >>> let A >>> >>> export >>> function setUpA() { >>> >>> console.log('setUpA') >>> console.log(C) >>> >>> A = class A extends C { >>> // ... >>> } >>> >>> } >>> >>> console.log('Module A', C, setUpA) >>> >>> export {A as default} >>> ``` >>> >>> ```js >>> // --- Module B >>> >>> import C from './C' >>> >>> let B >>> >>> export >>> function setUpB() { >>> >>> console.log('setUpB', C) >>> >>> B = class B extends C { >>> // ... >>> } >>> >>> } >>> >>> console.log('Module B', C, setUpB) >>> >>> export {B as default} >>> ``` >>> >>> ```js >>> // --- Module C >>> >>> import A, {setUpA} from './A' >>> import B, {setUpB} from './B' >>> >>> let C = class C { >>> constructor() { >>> // this may run later, after all three modules are evaluated, or >>> // possibly never. >>> console.log(A) >>> console.log(B) >>> } >>> } >>> >>> setUpA() >>> console.log('Module C', A) >>> >>> setUpB() >>> console.log('Module C', B) >>> >>> export {C as default} >>> ``` >>> >>> As you can see, modules A and B simply export the code that should be >>> evaluated (note the live bindings). Then finally, the C module is evaluated >>> last. At the end of the C module, you see that it calls `setUpA` and >>> `setUpB`. When it fires `setUpA`, an error is thrown on the second >>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>> `undefined` because the ES6 modules are compiled into CommonJS form). >>> >>> I thought that if `C` was a live binding, then it should be ready by the >>> time the `setUpA` function is called. Should this in fact be the case? >>> >>> */#!/*JoePea >>> >>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> wrote: >>> >>>> Without a way to load "later" (aka "soft") dependencies, ES6 module >>>> will continue to be more or less broken for circular dependencies. >>>> >>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com> >>>> wrote: >>>> >>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> > True, and so that's why I'm wondering if the module system can see >>>>> that it >>>>> > can satisfy all module requirements if it simply evaluates module C >>>>> first, >>>>> > followed by A or B in any order. It is easy for us humans to see >>>>> that. It >>>>> > would be nice for the module system to see that as well (I'm not >>>>> sure if >>>>> > that is spec'd or not). >>>>> >>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>> module, beyond what is expressed in the `import` statements. That's >>>>> assuming there's no dynamic trickery going on that would invalidate >>>>> whatever assumptions it can draw from surface-level analysis. >>>>> >>>>> Because of this, only the `import` statements are declaratively >>>>> available to the module system to work with. Based on that, it >>>>> definitely can't make any ordering assumptions; all it knows is that A >>>>> imports C, B imports C, and C imports both A and B, making a circular >>>>> import. >>>>> >>>>> ~TJ >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>> >>>> >>> >> > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/8cda2318/attachment-0001.html>
In your module B, class B
should extends C
instead of A, so both
classes A
and B
extend from C
.
I made a reproduction that you can run (assuming you have Meteor installed). See the following with instructions: meteor meteor/issues/7621#issuecomment-238923360
But, anyways, the example you just gave is almost identical to my [original
example](esdiscuss.org/topic/how-to-solve-this-
basic-es6-module-circular-dependency-problem#content-0) (except for my B
class extends from the C
class).
I can make a reproduction of that too if you want, but what happens is that
the environment will try to execute module A before executing module C, at
which point C
is undefined
inside of module A
. Basically, the result
would be the same as writing:
class A extends undefined {} // throws an Error
I noticed that both Meteor and Webpack will try and execute modules A and B first, then finally C, so I thought I could export the setup functions andrun them after defining the C class so that even if modules A and B run first the actual class definitions would run after the C class definition. You can see that I'm trying to take advantage of "live bindings" in order for this to work (but it didn't, hence I have to pass C into the setup functions).
I have a feeling that both Meteor's and Webpack's implementations aren't up-to-spec as far as live bindings with circular dependencies, but I could be wrong.
You shouldn't need a
setUpA
export, especially called by one of its
dependencies. Just declare and initialize that crap when it's being declared.
That's what I thought at first too, but it's not the case, and I'm trying to find a solution.
In your module B, `class B` should `extends C` instead of A, so both classes `A` and `B` extend from `C`. I made a reproduction that you can run (assuming you have Meteor installed). See the following with instructions: https://github.com/meteor/ meteor/issues/7621#issuecomment-238923360 But, anyways, the example you just gave is almost identical to my [original example](https://esdiscuss.org/topic/how-to-solve-this- basic-es6-module-circular-dependency-problem#content-0) (except for my `B` class extends from the `C` class). I can make a reproduction of that too if you want, but what happens is that the environment will try to execute module A before executing module C, at which point `C` is `undefined` inside of module `A`. Basically, the result would be the same as writing: ```js class A extends undefined {} // throws an Error ``` I noticed that both Meteor and Webpack will try and execute modules A and B *first*, then finally C, so I thought I could export the setup functions andrun them after defining the C class so that even if modules A and B run first the actual class definitions would run after the C class definition. You can see that I'm trying to take advantage of "live bindings" in order for this to work (but it didn't, hence I have to pass C into the setup functions). I have a feeling that both Meteor's and Webpack's implementations aren't up-to-spec as far as live bindings with circular dependencies, but I could be wrong. > You shouldn't need a `setUpA` export, especially called by one of its dependencies. Just declare and initialize that crap when it's being declared. That's what I thought at first too, but it's not the case, and I'm trying to find a solution. */#!/*JoePea On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows <isiahmeadows at gmail.com> wrote: > First of all, I'll point out that even if it's an internal API, you should > just initialize them immediately. You already have an otherwise fully > initialized C, so you should just add them whenever it comes. You shouldn't > need a `setUpA` export, especially called by one of its dependencies. Just > declare and initialize that crap when it's being declared. > > ```js > /* index.js */ > import A from './app/A' > console.log('Entrypoint', A) > ``` > > ```js > /* app/A.js */ > import C from './C' > > export default class A eclxtends C { > // ... > } > > // set up A here > console.log('Module A') > ``` > > ```js > /* app/B.js */ > import C from './C' > > export default class B extends A { > // ... > } > > // set up B here > console.log('Module B') > ``` > > ```js > /* app/C.js */ > import A from './A' > import B from './B' > > export default class C { > constructor() { > // this may run later, after all three modules are evaluated, or > // possibly never. > console.log(A) > console.log(B) > } > } > > // set up C > console.log('Module C') > ``` > > What's your full output, anyways? That would help me best explain what's > going on, though. > > On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: > >> When I try this same code with Webpack, I get the *exact same results*: >> the `console.log` statements in the exact same order, where the last output >> shows that `A` in the entry point is `undefined`). >> >> Am I misunderstanding something about live bindings? Is there some >> guaranteed order in which these modules should be evaluated? >> >> The reason why I'm after a solution for the circular dependency is >> because in my real-world case I need to use `instanceof A` and `intanceof >> B` within the `C` superclass defined in module C. This is a case of the >> Fragile Base Class Problem where a class should usually *not* have >> knowledge of it's subclasses, but the base class in my case is intended to >> be internal only, not a part of the public API that end users will extend >> from. >> >> */#!/*JoePea >> >> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> I can get the whole thing to work if I pass the C dependency into the >>> `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in >>> the Entrypoint module at the `console.log` statement, which makes it seem >>> to me like live bindings aren't working the I was expecting. >>> >>> ```js >>> // --- Entrypoint >>> >>> import A from './app/A' >>> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >>> ``` >>> >>> ```js >>> // --- Module A >>> >>> import C from './C' >>> >>> let A >>> >>> export >>> function setUpA(C) { >>> >>> console.log('setUpA') >>> console.log(C) >>> >>> A = class A extends C { >>> // ... >>> } >>> >>> } >>> >>> console.log('Module A', C, setUpA) >>> >>> export {A as default} >>> ``` >>> >>> ```js >>> // --- Module B >>> >>> import C from './C' >>> >>> let B >>> >>> export >>> function setUpB(C) { >>> >>> console.log('setUpB', C) >>> >>> B = class B extends C { >>> // ... >>> } >>> >>> } >>> >>> console.log('Module B', C, setUpB) >>> >>> export {B as default} >>> ``` >>> >>> ```js >>> // --- Module C >>> >>> import A, {setUpA} from './A' >>> import B, {setUpB} from './B' >>> >>> let C = class C { >>> constructor() { >>> // this may run later, after all three modules are evaluated, or >>> // possibly never. >>> console.log(A) >>> console.log(B) >>> } >>> } >>> >>> setUpA(C) >>> console.log('Module C', A) >>> >>> setUpB(C) >>> console.log('Module C', B) >>> >>> export {C as default} >>> ``` >>> >>> */#!/*JoePea >>> >>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> It seems that the environment I'm in (Meteor uses [reify]( >>>> https://github.com/benjamn/reify)) tries to evaluate A and B first, so >>>> I thought I could take advantage of "live bindings" by changing my modules >>>> to the following: >>>> >>>> ```js >>>> // --- Entrypoint >>>> >>>> import A from './app/A' >>>> console.log('Entrypoint', A) >>>> ``` >>>> >>>> ```js >>>> // --- Module A >>>> >>>> import C from './C' >>>> >>>> let A >>>> >>>> export >>>> function setUpA() { >>>> >>>> console.log('setUpA') >>>> console.log(C) >>>> >>>> A = class A extends C { >>>> // ... >>>> } >>>> >>>> } >>>> >>>> console.log('Module A', C, setUpA) >>>> >>>> export {A as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module B >>>> >>>> import C from './C' >>>> >>>> let B >>>> >>>> export >>>> function setUpB() { >>>> >>>> console.log('setUpB', C) >>>> >>>> B = class B extends C { >>>> // ... >>>> } >>>> >>>> } >>>> >>>> console.log('Module B', C, setUpB) >>>> >>>> export {B as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module C >>>> >>>> import A, {setUpA} from './A' >>>> import B, {setUpB} from './B' >>>> >>>> let C = class C { >>>> constructor() { >>>> // this may run later, after all three modules are evaluated, or >>>> // possibly never. >>>> console.log(A) >>>> console.log(B) >>>> } >>>> } >>>> >>>> setUpA() >>>> console.log('Module C', A) >>>> >>>> setUpB() >>>> console.log('Module C', B) >>>> >>>> export {C as default} >>>> ``` >>>> >>>> As you can see, modules A and B simply export the code that should be >>>> evaluated (note the live bindings). Then finally, the C module is evaluated >>>> last. At the end of the C module, you see that it calls `setUpA` and >>>> `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>> >>>> I thought that if `C` was a live binding, then it should be ready by >>>> the time the `setUpA` function is called. Should this in fact be the case? >>>> >>>> */#!/*JoePea >>>> >>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>> wrote: >>>> >>>>> Without a way to load "later" (aka "soft") dependencies, ES6 module >>>>> will continue to be more or less broken for circular dependencies. >>>>> >>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com> >>>>> wrote: >>>>> >>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>> > True, and so that's why I'm wondering if the module system can see >>>>>> that it >>>>>> > can satisfy all module requirements if it simply evaluates module C >>>>>> first, >>>>>> > followed by A or B in any order. It is easy for us humans to see >>>>>> that. It >>>>>> > would be nice for the module system to see that as well (I'm not >>>>>> sure if >>>>>> > that is spec'd or not). >>>>>> >>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>> module, beyond what is expressed in the `import` statements. That's >>>>>> assuming there's no dynamic trickery going on that would invalidate >>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>> >>>>>> Because of this, only the `import` statements are declaratively >>>>>> available to the module system to work with. Based on that, it >>>>>> definitely can't make any ordering assumptions; all it knows is that A >>>>>> imports C, B imports C, and C imports both A and B, making a circular >>>>>> import. >>>>>> >>>>>> ~TJ >>>>>> _______________________________________________ >>>>>> es-discuss mailing list >>>>>> es-discuss at mozilla.org >>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>> >>>>> >>>>> >>>> >>> >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/923bf7a9/attachment-0001.html>
Isaiah, also note that
export default class Foo {}
does not create a live binding that can be modified at a later point in
time, which is the feature that my setUpA
and setUpB
functions are
theoretically relying on (and which I believe the Meteor and Webpack
environments don't handle properly if I understand live bindings correctly).
Isaiah, also note that ```js export default class Foo {} ``` does not create a live binding that can be modified at a later point in time, which is the feature that my `setUpA` and `setUpB` functions are theoretically relying on (and which I believe the Meteor and Webpack environments don't handle properly if I understand live bindings correctly). */#!/*JoePea On Wed, Aug 10, 2016 at 12:31 PM, /#!/JoePea <joe at trusktr.io> wrote: > In your module B, `class B` should `extends C` instead of A, so both > classes `A` and `B` extend from `C`. > > I made a reproduction that you can run (assuming you have Meteor > installed). See the following with instructions: > https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 > > But, anyways, the example you just gave is almost identical to my > [original example](https://esdiscuss.org/topic/how-to-solve-this-basic > -es6-module-circular-dependency-problem#content-0) (except for my `B` > class extends from the `C` class). > > I can make a reproduction of that too if you want, but what happens is > that the environment will try to execute module A before executing module > C, at which point `C` is `undefined` inside of module `A`. Basically, the > result would be the same as writing: > > ```js > class A extends undefined {} // throws an Error > ``` > > I noticed that both Meteor and Webpack will try and execute modules A and > B *first*, then finally C, so I thought I could export the setup functions > andrun them after defining the C class so that even if modules A and B run > first the actual class definitions would run after the C class definition. > You can see that I'm trying to take advantage of "live bindings" in order > for this to work (but it didn't, hence I have to pass C into the setup > functions). > > I have a feeling that both Meteor's and Webpack's implementations aren't > up-to-spec as far as live bindings with circular dependencies, but I could > be wrong. > > > You shouldn't need a `setUpA` export, especially called by one of its > dependencies. Just declare and initialize that crap when it's being > declared. > > That's what I thought at first too, but it's not the case, and I'm trying > to find a solution. > > */#!/*JoePea > > On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows <isiahmeadows at gmail.com> > wrote: > >> First of all, I'll point out that even if it's an internal API, you >> should just initialize them immediately. You already have an otherwise >> fully initialized C, so you should just add them whenever it comes. You >> shouldn't need a `setUpA` export, especially called by one of its >> dependencies. Just declare and initialize that crap when it's being >> declared. >> >> ```js >> /* index.js */ >> import A from './app/A' >> console.log('Entrypoint', A) >> ``` >> >> ```js >> /* app/A.js */ >> import C from './C' >> >> export default class A eclxtends C { >> // ... >> } >> >> // set up A here >> console.log('Module A') >> ``` >> >> ```js >> /* app/B.js */ >> import C from './C' >> >> export default class B extends A { >> // ... >> } >> >> // set up B here >> console.log('Module B') >> ``` >> >> ```js >> /* app/C.js */ >> import A from './A' >> import B from './B' >> >> export default class C { >> constructor() { >> // this may run later, after all three modules are evaluated, or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> >> // set up C >> console.log('Module C') >> ``` >> >> What's your full output, anyways? That would help me best explain what's >> going on, though. >> >> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >> >>> When I try this same code with Webpack, I get the *exact same results*: >>> the `console.log` statements in the exact same order, where the last output >>> shows that `A` in the entry point is `undefined`). >>> >>> Am I misunderstanding something about live bindings? Is there some >>> guaranteed order in which these modules should be evaluated? >>> >>> The reason why I'm after a solution for the circular dependency is >>> because in my real-world case I need to use `instanceof A` and `intanceof >>> B` within the `C` superclass defined in module C. This is a case of the >>> Fragile Base Class Problem where a class should usually *not* have >>> knowledge of it's subclasses, but the base class in my case is intended to >>> be internal only, not a part of the public API that end users will extend >>> from. >>> >>> */#!/*JoePea >>> >>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> I can get the whole thing to work if I pass the C dependency into the >>>> `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in >>>> the Entrypoint module at the `console.log` statement, which makes it seem >>>> to me like live bindings aren't working the I was expecting. >>>> >>>> ```js >>>> // --- Entrypoint >>>> >>>> import A from './app/A' >>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >>>> ``` >>>> >>>> ```js >>>> // --- Module A >>>> >>>> import C from './C' >>>> >>>> let A >>>> >>>> export >>>> function setUpA(C) { >>>> >>>> console.log('setUpA') >>>> console.log(C) >>>> >>>> A = class A extends C { >>>> // ... >>>> } >>>> >>>> } >>>> >>>> console.log('Module A', C, setUpA) >>>> >>>> export {A as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module B >>>> >>>> import C from './C' >>>> >>>> let B >>>> >>>> export >>>> function setUpB(C) { >>>> >>>> console.log('setUpB', C) >>>> >>>> B = class B extends C { >>>> // ... >>>> } >>>> >>>> } >>>> >>>> console.log('Module B', C, setUpB) >>>> >>>> export {B as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module C >>>> >>>> import A, {setUpA} from './A' >>>> import B, {setUpB} from './B' >>>> >>>> let C = class C { >>>> constructor() { >>>> // this may run later, after all three modules are evaluated, or >>>> // possibly never. >>>> console.log(A) >>>> console.log(B) >>>> } >>>> } >>>> >>>> setUpA(C) >>>> console.log('Module C', A) >>>> >>>> setUpB(C) >>>> console.log('Module C', B) >>>> >>>> export {C as default} >>>> ``` >>>> >>>> */#!/*JoePea >>>> >>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>> https://github.com/benjamn/reify)) tries to evaluate A and B first, >>>>> so I thought I could take advantage of "live bindings" by changing my >>>>> modules to the following: >>>>> >>>>> ```js >>>>> // --- Entrypoint >>>>> >>>>> import A from './app/A' >>>>> console.log('Entrypoint', A) >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module A >>>>> >>>>> import C from './C' >>>>> >>>>> let A >>>>> >>>>> export >>>>> function setUpA() { >>>>> >>>>> console.log('setUpA') >>>>> console.log(C) >>>>> >>>>> A = class A extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> console.log('Module A', C, setUpA) >>>>> >>>>> export {A as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module B >>>>> >>>>> import C from './C' >>>>> >>>>> let B >>>>> >>>>> export >>>>> function setUpB() { >>>>> >>>>> console.log('setUpB', C) >>>>> >>>>> B = class B extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> console.log('Module B', C, setUpB) >>>>> >>>>> export {B as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module C >>>>> >>>>> import A, {setUpA} from './A' >>>>> import B, {setUpB} from './B' >>>>> >>>>> let C = class C { >>>>> constructor() { >>>>> // this may run later, after all three modules are evaluated, >>>>> or >>>>> // possibly never. >>>>> console.log(A) >>>>> console.log(B) >>>>> } >>>>> } >>>>> >>>>> setUpA() >>>>> console.log('Module C', A) >>>>> >>>>> setUpB() >>>>> console.log('Module C', B) >>>>> >>>>> export {C as default} >>>>> ``` >>>>> >>>>> As you can see, modules A and B simply export the code that should be >>>>> evaluated (note the live bindings). Then finally, the C module is evaluated >>>>> last. At the end of the C module, you see that it calls `setUpA` and >>>>> `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>> >>>>> I thought that if `C` was a live binding, then it should be ready by >>>>> the time the `setUpA` function is called. Should this in fact be the case? >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>>> wrote: >>>>> >>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 module >>>>>> will continue to be more or less broken for circular dependencies. >>>>>> >>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com> >>>>>> wrote: >>>>>> >>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>> > True, and so that's why I'm wondering if the module system can see >>>>>>> that it >>>>>>> > can satisfy all module requirements if it simply evaluates module >>>>>>> C first, >>>>>>> > followed by A or B in any order. It is easy for us humans to see >>>>>>> that. It >>>>>>> > would be nice for the module system to see that as well (I'm not >>>>>>> sure if >>>>>>> > that is spec'd or not). >>>>>>> >>>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>>> module, beyond what is expressed in the `import` statements. That's >>>>>>> assuming there's no dynamic trickery going on that would invalidate >>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>> >>>>>>> Because of this, only the `import` statements are declaratively >>>>>>> available to the module system to work with. Based on that, it >>>>>>> definitely can't make any ordering assumptions; all it knows is that >>>>>>> A >>>>>>> imports C, B imports C, and C imports both A and B, making a circular >>>>>>> import. >>>>>>> >>>>>>> ~TJ >>>>>>> _______________________________________________ >>>>>>> es-discuss mailing list >>>>>>> es-discuss at mozilla.org >>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>> >>>>>> >>>>>> >>>>> >>>> >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/f5250be0/attachment-0001.html>
What do you get out of Rollup.js (which actually implements the ES6 spec with bindings, unlike Webpack, etc., which handle CommonJS modules)? You should be able to run the bundled repro in Node, or you could inspect the bundle yourself just as easily to see the execution order.
What do you get out of Rollup.js (which actually implements the ES6 spec with bindings, unlike Webpack, etc., which handle CommonJS modules)? You should be able to run the bundled repro in Node, or you could inspect the bundle yourself just as easily to see the execution order. On Wed, Aug 10, 2016, 15:31 /#!/JoePea <joe at trusktr.io> wrote: > In your module B, `class B` should `extends C` instead of A, so both > classes `A` and `B` extend from `C`. > > I made a reproduction that you can run (assuming you have Meteor > installed). See the following with instructions: > https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 > > But, anyways, the example you just gave is almost identical to my > [original example]( > https://esdiscuss.org/topic/how-to-solve-this-basic-es6-module-circular-dependency-problem#content-0) > (except for my `B` class extends from the `C` class). > > I can make a reproduction of that too if you want, but what happens is > that the environment will try to execute module A before executing module > C, at which point `C` is `undefined` inside of module `A`. Basically, the > result would be the same as writing: > > ```js > class A extends undefined {} // throws an Error > ``` > > I noticed that both Meteor and Webpack will try and execute modules A and > B *first*, then finally C, so I thought I could export the setup functions > andrun them after defining the C class so that even if modules A and B run > first the actual class definitions would run after the C class definition. > You can see that I'm trying to take advantage of "live bindings" in order > for this to work (but it didn't, hence I have to pass C into the setup > functions). > > I have a feeling that both Meteor's and Webpack's implementations aren't > up-to-spec as far as live bindings with circular dependencies, but I could > be wrong. > > > You shouldn't need a `setUpA` export, especially called by one of its > dependencies. Just declare and initialize that crap when it's being > declared. > > That's what I thought at first too, but it's not the case, and I'm trying > to find a solution. > > */#!/*JoePea > > On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows <isiahmeadows at gmail.com> > wrote: > >> First of all, I'll point out that even if it's an internal API, you >> should just initialize them immediately. You already have an otherwise >> fully initialized C, so you should just add them whenever it comes. You >> shouldn't need a `setUpA` export, especially called by one of its >> dependencies. Just declare and initialize that crap when it's being >> declared. >> >> ```js >> /* index.js */ >> import A from './app/A' >> console.log('Entrypoint', A) >> ``` >> >> ```js >> /* app/A.js */ >> import C from './C' >> >> export default class A eclxtends C { >> // ... >> } >> >> // set up A here >> console.log('Module A') >> ``` >> >> ```js >> /* app/B.js */ >> import C from './C' >> >> export default class B extends A { >> // ... >> } >> >> // set up B here >> console.log('Module B') >> ``` >> >> ```js >> /* app/C.js */ >> import A from './A' >> import B from './B' >> >> export default class C { >> constructor() { >> // this may run later, after all three modules are evaluated, or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> >> // set up C >> console.log('Module C') >> ``` >> >> What's your full output, anyways? That would help me best explain what's >> going on, though. >> >> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >> >>> When I try this same code with Webpack, I get the *exact same results*: >>> the `console.log` statements in the exact same order, where the last output >>> shows that `A` in the entry point is `undefined`). >>> >>> Am I misunderstanding something about live bindings? Is there some >>> guaranteed order in which these modules should be evaluated? >>> >>> The reason why I'm after a solution for the circular dependency is >>> because in my real-world case I need to use `instanceof A` and `intanceof >>> B` within the `C` superclass defined in module C. This is a case of the >>> Fragile Base Class Problem where a class should usually *not* have >>> knowledge of it's subclasses, but the base class in my case is intended to >>> be internal only, not a part of the public API that end users will extend >>> from. >>> >>> */#!/*JoePea >>> >>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> I can get the whole thing to work if I pass the C dependency into the >>>> `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in >>>> the Entrypoint module at the `console.log` statement, which makes it seem >>>> to me like live bindings aren't working the I was expecting. >>>> >>>> ```js >>>> // --- Entrypoint >>>> >>>> import A from './app/A' >>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >>>> ``` >>>> >>>> ```js >>>> // --- Module A >>>> >>>> import C from './C' >>>> >>>> let A >>>> >>>> export >>>> function setUpA(C) { >>>> >>>> console.log('setUpA') >>>> console.log(C) >>>> >>>> A = class A extends C { >>>> // ... >>>> } >>>> >>>> } >>>> >>>> console.log('Module A', C, setUpA) >>>> >>>> export {A as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module B >>>> >>>> import C from './C' >>>> >>>> let B >>>> >>>> export >>>> function setUpB(C) { >>>> >>>> console.log('setUpB', C) >>>> >>>> B = class B extends C { >>>> // ... >>>> } >>>> >>>> } >>>> >>>> console.log('Module B', C, setUpB) >>>> >>>> export {B as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module C >>>> >>>> import A, {setUpA} from './A' >>>> import B, {setUpB} from './B' >>>> >>>> let C = class C { >>>> constructor() { >>>> // this may run later, after all three modules are evaluated, or >>>> // possibly never. >>>> console.log(A) >>>> console.log(B) >>>> } >>>> } >>>> >>>> setUpA(C) >>>> console.log('Module C', A) >>>> >>>> setUpB(C) >>>> console.log('Module C', B) >>>> >>>> export {C as default} >>>> ``` >>>> >>>> */#!/*JoePea >>>> >>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>> https://github.com/benjamn/reify)) tries to evaluate A and B first, >>>>> so I thought I could take advantage of "live bindings" by changing my >>>>> modules to the following: >>>>> >>>>> ```js >>>>> // --- Entrypoint >>>>> >>>>> import A from './app/A' >>>>> console.log('Entrypoint', A) >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module A >>>>> >>>>> import C from './C' >>>>> >>>>> let A >>>>> >>>>> export >>>>> function setUpA() { >>>>> >>>>> console.log('setUpA') >>>>> console.log(C) >>>>> >>>>> A = class A extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> console.log('Module A', C, setUpA) >>>>> >>>>> export {A as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module B >>>>> >>>>> import C from './C' >>>>> >>>>> let B >>>>> >>>>> export >>>>> function setUpB() { >>>>> >>>>> console.log('setUpB', C) >>>>> >>>>> B = class B extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> console.log('Module B', C, setUpB) >>>>> >>>>> export {B as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module C >>>>> >>>>> import A, {setUpA} from './A' >>>>> import B, {setUpB} from './B' >>>>> >>>>> let C = class C { >>>>> constructor() { >>>>> // this may run later, after all three modules are evaluated, >>>>> or >>>>> // possibly never. >>>>> console.log(A) >>>>> console.log(B) >>>>> } >>>>> } >>>>> >>>>> setUpA() >>>>> console.log('Module C', A) >>>>> >>>>> setUpB() >>>>> console.log('Module C', B) >>>>> >>>>> export {C as default} >>>>> ``` >>>>> >>>>> As you can see, modules A and B simply export the code that should be >>>>> evaluated (note the live bindings). Then finally, the C module is evaluated >>>>> last. At the end of the C module, you see that it calls `setUpA` and >>>>> `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>> >>>>> I thought that if `C` was a live binding, then it should be ready by >>>>> the time the `setUpA` function is called. Should this in fact be the case? >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>>> wrote: >>>>> >>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 module >>>>>> will continue to be more or less broken for circular dependencies. >>>>>> >>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com> >>>>>> wrote: >>>>>> >>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>> > True, and so that's why I'm wondering if the module system can see >>>>>>> that it >>>>>>> > can satisfy all module requirements if it simply evaluates module >>>>>>> C first, >>>>>>> > followed by A or B in any order. It is easy for us humans to see >>>>>>> that. It >>>>>>> > would be nice for the module system to see that as well (I'm not >>>>>>> sure if >>>>>>> > that is spec'd or not). >>>>>>> >>>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>>> module, beyond what is expressed in the `import` statements. That's >>>>>>> assuming there's no dynamic trickery going on that would invalidate >>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>> >>>>>>> Because of this, only the `import` statements are declaratively >>>>>>> available to the module system to work with. Based on that, it >>>>>>> definitely can't make any ordering assumptions; all it knows is that >>>>>>> A >>>>>>> imports C, B imports C, and C imports both A and B, making a circular >>>>>>> import. >>>>>>> >>>>>>> ~TJ >>>>>>> _______________________________________________ >>>>>>> es-discuss mailing list >>>>>>> es-discuss at mozilla.org >>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>> >>>>>> >>>>>> >>>>> >>>> >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/b5b909de/attachment-0001.html>
Oh! And although I think that my setUpA
and setUpB
technique should
work due to the fact that Webpack and Meteor load the modules in the exact
same order where the C module is executed last, this may in fact fail in
some other ES6 environment that happens to execute the C module first, in
which case setUpA
and setUpB
will be undefined when C is evaluated.
So, I don't know if my solution is good. I am wondering if there's something in the spec that guarantees that the C module evaluates last?
Oh! And although I think that my `setUpA` and `setUpB` technique should work due to the fact that Webpack and Meteor load the modules in the exact same order where the C module is executed last, this may in fact fail in some other ES6 environment that happens to execute the C module first, in which case `setUpA` and `setUpB` will be undefined when C is evaluated. So, I don't know if my solution is good. I am wondering if there's something in the spec that guarantees that the C module evaluates last? */#!/*JoePea On Wed, Aug 10, 2016 at 12:38 PM, /#!/JoePea <joe at trusktr.io> wrote: > Isaiah, also note that > > ```js > export default class Foo {} > ``` > > does not create a live binding that can be modified at a later point in > time, which is the feature that my `setUpA` and `setUpB` functions are > theoretically relying on (and which I believe the Meteor and Webpack > environments don't handle properly if I understand live bindings correctly). > > */#!/*JoePea > > On Wed, Aug 10, 2016 at 12:31 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> In your module B, `class B` should `extends C` instead of A, so both >> classes `A` and `B` extend from `C`. >> >> I made a reproduction that you can run (assuming you have Meteor >> installed). See the following with instructions: >> https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 >> >> But, anyways, the example you just gave is almost identical to my >> [original example](https://esdiscuss.org/topic/how-to-solve-this-basic >> -es6-module-circular-dependency-problem#content-0) (except for my `B` >> class extends from the `C` class). >> >> I can make a reproduction of that too if you want, but what happens is >> that the environment will try to execute module A before executing module >> C, at which point `C` is `undefined` inside of module `A`. Basically, the >> result would be the same as writing: >> >> ```js >> class A extends undefined {} // throws an Error >> ``` >> >> I noticed that both Meteor and Webpack will try and execute modules A and >> B *first*, then finally C, so I thought I could export the setup functions >> andrun them after defining the C class so that even if modules A and B run >> first the actual class definitions would run after the C class definition. >> You can see that I'm trying to take advantage of "live bindings" in order >> for this to work (but it didn't, hence I have to pass C into the setup >> functions). >> >> I have a feeling that both Meteor's and Webpack's implementations aren't >> up-to-spec as far as live bindings with circular dependencies, but I could >> be wrong. >> >> > You shouldn't need a `setUpA` export, especially called by one of its >> dependencies. Just declare and initialize that crap when it's being >> declared. >> >> That's what I thought at first too, but it's not the case, and I'm trying >> to find a solution. >> >> */#!/*JoePea >> >> On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows <isiahmeadows at gmail.com> >> wrote: >> >>> First of all, I'll point out that even if it's an internal API, you >>> should just initialize them immediately. You already have an otherwise >>> fully initialized C, so you should just add them whenever it comes. You >>> shouldn't need a `setUpA` export, especially called by one of its >>> dependencies. Just declare and initialize that crap when it's being >>> declared. >>> >>> ```js >>> /* index.js */ >>> import A from './app/A' >>> console.log('Entrypoint', A) >>> ``` >>> >>> ```js >>> /* app/A.js */ >>> import C from './C' >>> >>> export default class A eclxtends C { >>> // ... >>> } >>> >>> // set up A here >>> console.log('Module A') >>> ``` >>> >>> ```js >>> /* app/B.js */ >>> import C from './C' >>> >>> export default class B extends A { >>> // ... >>> } >>> >>> // set up B here >>> console.log('Module B') >>> ``` >>> >>> ```js >>> /* app/C.js */ >>> import A from './A' >>> import B from './B' >>> >>> export default class C { >>> constructor() { >>> // this may run later, after all three modules are evaluated, or >>> // possibly never. >>> console.log(A) >>> console.log(B) >>> } >>> } >>> >>> // set up C >>> console.log('Module C') >>> ``` >>> >>> What's your full output, anyways? That would help me best explain what's >>> going on, though. >>> >>> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> When I try this same code with Webpack, I get the *exact same results*: >>>> the `console.log` statements in the exact same order, where the last output >>>> shows that `A` in the entry point is `undefined`). >>>> >>>> Am I misunderstanding something about live bindings? Is there some >>>> guaranteed order in which these modules should be evaluated? >>>> >>>> The reason why I'm after a solution for the circular dependency is >>>> because in my real-world case I need to use `instanceof A` and `intanceof >>>> B` within the `C` superclass defined in module C. This is a case of the >>>> Fragile Base Class Problem where a class should usually *not* have >>>> knowledge of it's subclasses, but the base class in my case is intended to >>>> be internal only, not a part of the public API that end users will extend >>>> from. >>>> >>>> */#!/*JoePea >>>> >>>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> I can get the whole thing to work if I pass the C dependency into the >>>>> `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in >>>>> the Entrypoint module at the `console.log` statement, which makes it seem >>>>> to me like live bindings aren't working the I was expecting. >>>>> >>>>> ```js >>>>> // --- Entrypoint >>>>> >>>>> import A from './app/A' >>>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module A >>>>> >>>>> import C from './C' >>>>> >>>>> let A >>>>> >>>>> export >>>>> function setUpA(C) { >>>>> >>>>> console.log('setUpA') >>>>> console.log(C) >>>>> >>>>> A = class A extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> console.log('Module A', C, setUpA) >>>>> >>>>> export {A as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module B >>>>> >>>>> import C from './C' >>>>> >>>>> let B >>>>> >>>>> export >>>>> function setUpB(C) { >>>>> >>>>> console.log('setUpB', C) >>>>> >>>>> B = class B extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> console.log('Module B', C, setUpB) >>>>> >>>>> export {B as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module C >>>>> >>>>> import A, {setUpA} from './A' >>>>> import B, {setUpB} from './B' >>>>> >>>>> let C = class C { >>>>> constructor() { >>>>> // this may run later, after all three modules are evaluated, >>>>> or >>>>> // possibly never. >>>>> console.log(A) >>>>> console.log(B) >>>>> } >>>>> } >>>>> >>>>> setUpA(C) >>>>> console.log('Module C', A) >>>>> >>>>> setUpB(C) >>>>> console.log('Module C', B) >>>>> >>>>> export {C as default} >>>>> ``` >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>>> https://github.com/benjamn/reify)) tries to evaluate A and B first, >>>>>> so I thought I could take advantage of "live bindings" by changing my >>>>>> modules to the following: >>>>>> >>>>>> ```js >>>>>> // --- Entrypoint >>>>>> >>>>>> import A from './app/A' >>>>>> console.log('Entrypoint', A) >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module A >>>>>> >>>>>> import C from './C' >>>>>> >>>>>> let A >>>>>> >>>>>> export >>>>>> function setUpA() { >>>>>> >>>>>> console.log('setUpA') >>>>>> console.log(C) >>>>>> >>>>>> A = class A extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> console.log('Module A', C, setUpA) >>>>>> >>>>>> export {A as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module B >>>>>> >>>>>> import C from './C' >>>>>> >>>>>> let B >>>>>> >>>>>> export >>>>>> function setUpB() { >>>>>> >>>>>> console.log('setUpB', C) >>>>>> >>>>>> B = class B extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> console.log('Module B', C, setUpB) >>>>>> >>>>>> export {B as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module C >>>>>> >>>>>> import A, {setUpA} from './A' >>>>>> import B, {setUpB} from './B' >>>>>> >>>>>> let C = class C { >>>>>> constructor() { >>>>>> // this may run later, after all three modules are evaluated, >>>>>> or >>>>>> // possibly never. >>>>>> console.log(A) >>>>>> console.log(B) >>>>>> } >>>>>> } >>>>>> >>>>>> setUpA() >>>>>> console.log('Module C', A) >>>>>> >>>>>> setUpB() >>>>>> console.log('Module C', B) >>>>>> >>>>>> export {C as default} >>>>>> ``` >>>>>> >>>>>> As you can see, modules A and B simply export the code that should be >>>>>> evaluated (note the live bindings). Then finally, the C module is evaluated >>>>>> last. At the end of the C module, you see that it calls `setUpA` and >>>>>> `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>>> >>>>>> I thought that if `C` was a live binding, then it should be ready by >>>>>> the time the `setUpA` function is called. Should this in fact be the case? >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>>>> wrote: >>>>>> >>>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 module >>>>>>> will continue to be more or less broken for circular dependencies. >>>>>>> >>>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com >>>>>>> > wrote: >>>>>>> >>>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>>> > True, and so that's why I'm wondering if the module system can >>>>>>>> see that it >>>>>>>> > can satisfy all module requirements if it simply evaluates module >>>>>>>> C first, >>>>>>>> > followed by A or B in any order. It is easy for us humans to see >>>>>>>> that. It >>>>>>>> > would be nice for the module system to see that as well (I'm not >>>>>>>> sure if >>>>>>>> > that is spec'd or not). >>>>>>>> >>>>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>>>> module, beyond what is expressed in the `import` statements. That's >>>>>>>> assuming there's no dynamic trickery going on that would invalidate >>>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>>> >>>>>>>> Because of this, only the `import` statements are declaratively >>>>>>>> available to the module system to work with. Based on that, it >>>>>>>> definitely can't make any ordering assumptions; all it knows is >>>>>>>> that A >>>>>>>> imports C, B imports C, and C imports both A and B, making a >>>>>>>> circular >>>>>>>> import. >>>>>>>> >>>>>>>> ~TJ >>>>>>>> _______________________________________________ >>>>>>>> es-discuss mailing list >>>>>>>> es-discuss at mozilla.org >>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>> >>>>>>> >>>>>>> >>>>>> >>>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/8c700f3c/attachment-0001.html>
To clarify, Webpack and friends translate ES6 modules to CommonJS modules pretty naïvely, so the bindings won't get translated correctly (CommonJS modules export values, not read-only bindings). Rollup actually handles the exports correctly as static bindings instead. So that's why I recommended testing it in Rollup.
To clarify, Webpack and friends translate ES6 modules to CommonJS modules pretty naïvely, so the bindings won't get translated correctly (CommonJS modules export values, not read-only bindings). Rollup actually handles the exports correctly as static bindings instead. So that's why I recommended testing it in Rollup. On Wed, Aug 10, 2016, 15:40 Isiah Meadows <isiahmeadows at gmail.com> wrote: > What do you get out of Rollup.js (which actually implements the ES6 spec > with bindings, unlike Webpack, etc., which handle CommonJS modules)? You > should be able to run the bundled repro in Node, or you could inspect the > bundle yourself just as easily to see the execution order. > > On Wed, Aug 10, 2016, 15:31 /#!/JoePea <joe at trusktr.io> wrote: > >> In your module B, `class B` should `extends C` instead of A, so both >> classes `A` and `B` extend from `C`. >> >> I made a reproduction that you can run (assuming you have Meteor >> installed). See the following with instructions: >> https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 >> >> But, anyways, the example you just gave is almost identical to my >> [original example]( >> https://esdiscuss.org/topic/how-to-solve-this-basic-es6-module-circular-dependency-problem#content-0) >> (except for my `B` class extends from the `C` class). >> >> I can make a reproduction of that too if you want, but what happens is >> that the environment will try to execute module A before executing module >> C, at which point `C` is `undefined` inside of module `A`. Basically, the >> result would be the same as writing: >> >> ```js >> class A extends undefined {} // throws an Error >> ``` >> >> I noticed that both Meteor and Webpack will try and execute modules A and >> B *first*, then finally C, so I thought I could export the setup functions >> andrun them after defining the C class so that even if modules A and B run >> first the actual class definitions would run after the C class definition. >> You can see that I'm trying to take advantage of "live bindings" in order >> for this to work (but it didn't, hence I have to pass C into the setup >> functions). >> >> I have a feeling that both Meteor's and Webpack's implementations aren't >> up-to-spec as far as live bindings with circular dependencies, but I could >> be wrong. >> >> > You shouldn't need a `setUpA` export, especially called by one of its >> dependencies. Just declare and initialize that crap when it's being >> declared. >> >> That's what I thought at first too, but it's not the case, and I'm trying >> to find a solution. >> >> */#!/*JoePea >> >> On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows <isiahmeadows at gmail.com> >> wrote: >> >>> First of all, I'll point out that even if it's an internal API, you >>> should just initialize them immediately. You already have an otherwise >>> fully initialized C, so you should just add them whenever it comes. You >>> shouldn't need a `setUpA` export, especially called by one of its >>> dependencies. Just declare and initialize that crap when it's being >>> declared. >>> >>> ```js >>> /* index.js */ >>> import A from './app/A' >>> console.log('Entrypoint', A) >>> ``` >>> >>> ```js >>> /* app/A.js */ >>> import C from './C' >>> >>> export default class A eclxtends C { >>> // ... >>> } >>> >>> // set up A here >>> console.log('Module A') >>> ``` >>> >>> ```js >>> /* app/B.js */ >>> import C from './C' >>> >>> export default class B extends A { >>> // ... >>> } >>> >>> // set up B here >>> console.log('Module B') >>> ``` >>> >>> ```js >>> /* app/C.js */ >>> import A from './A' >>> import B from './B' >>> >>> export default class C { >>> constructor() { >>> // this may run later, after all three modules are evaluated, or >>> // possibly never. >>> console.log(A) >>> console.log(B) >>> } >>> } >>> >>> // set up C >>> console.log('Module C') >>> ``` >>> >>> What's your full output, anyways? That would help me best explain what's >>> going on, though. >>> >>> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> When I try this same code with Webpack, I get the *exact same results*: >>>> the `console.log` statements in the exact same order, where the last output >>>> shows that `A` in the entry point is `undefined`). >>>> >>>> Am I misunderstanding something about live bindings? Is there some >>>> guaranteed order in which these modules should be evaluated? >>>> >>>> The reason why I'm after a solution for the circular dependency is >>>> because in my real-world case I need to use `instanceof A` and `intanceof >>>> B` within the `C` superclass defined in module C. This is a case of the >>>> Fragile Base Class Problem where a class should usually *not* have >>>> knowledge of it's subclasses, but the base class in my case is intended to >>>> be internal only, not a part of the public API that end users will extend >>>> from. >>>> >>>> */#!/*JoePea >>>> >>>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> I can get the whole thing to work if I pass the C dependency into the >>>>> `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in >>>>> the Entrypoint module at the `console.log` statement, which makes it seem >>>>> to me like live bindings aren't working the I was expecting. >>>>> >>>>> ```js >>>>> // --- Entrypoint >>>>> >>>>> import A from './app/A' >>>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module A >>>>> >>>>> import C from './C' >>>>> >>>>> let A >>>>> >>>>> export >>>>> function setUpA(C) { >>>>> >>>>> console.log('setUpA') >>>>> console.log(C) >>>>> >>>>> A = class A extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> console.log('Module A', C, setUpA) >>>>> >>>>> export {A as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module B >>>>> >>>>> import C from './C' >>>>> >>>>> let B >>>>> >>>>> export >>>>> function setUpB(C) { >>>>> >>>>> console.log('setUpB', C) >>>>> >>>>> B = class B extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> console.log('Module B', C, setUpB) >>>>> >>>>> export {B as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module C >>>>> >>>>> import A, {setUpA} from './A' >>>>> import B, {setUpB} from './B' >>>>> >>>>> let C = class C { >>>>> constructor() { >>>>> // this may run later, after all three modules are evaluated, >>>>> or >>>>> // possibly never. >>>>> console.log(A) >>>>> console.log(B) >>>>> } >>>>> } >>>>> >>>>> setUpA(C) >>>>> console.log('Module C', A) >>>>> >>>>> setUpB(C) >>>>> console.log('Module C', B) >>>>> >>>>> export {C as default} >>>>> ``` >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>>> https://github.com/benjamn/reify)) tries to evaluate A and B first, >>>>>> so I thought I could take advantage of "live bindings" by changing my >>>>>> modules to the following: >>>>>> >>>>>> ```js >>>>>> // --- Entrypoint >>>>>> >>>>>> import A from './app/A' >>>>>> console.log('Entrypoint', A) >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module A >>>>>> >>>>>> import C from './C' >>>>>> >>>>>> let A >>>>>> >>>>>> export >>>>>> function setUpA() { >>>>>> >>>>>> console.log('setUpA') >>>>>> console.log(C) >>>>>> >>>>>> A = class A extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> console.log('Module A', C, setUpA) >>>>>> >>>>>> export {A as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module B >>>>>> >>>>>> import C from './C' >>>>>> >>>>>> let B >>>>>> >>>>>> export >>>>>> function setUpB() { >>>>>> >>>>>> console.log('setUpB', C) >>>>>> >>>>>> B = class B extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> console.log('Module B', C, setUpB) >>>>>> >>>>>> export {B as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module C >>>>>> >>>>>> import A, {setUpA} from './A' >>>>>> import B, {setUpB} from './B' >>>>>> >>>>>> let C = class C { >>>>>> constructor() { >>>>>> // this may run later, after all three modules are evaluated, >>>>>> or >>>>>> // possibly never. >>>>>> console.log(A) >>>>>> console.log(B) >>>>>> } >>>>>> } >>>>>> >>>>>> setUpA() >>>>>> console.log('Module C', A) >>>>>> >>>>>> setUpB() >>>>>> console.log('Module C', B) >>>>>> >>>>>> export {C as default} >>>>>> ``` >>>>>> >>>>>> As you can see, modules A and B simply export the code that should be >>>>>> evaluated (note the live bindings). Then finally, the C module is evaluated >>>>>> last. At the end of the C module, you see that it calls `setUpA` and >>>>>> `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>>> >>>>>> I thought that if `C` was a live binding, then it should be ready by >>>>>> the time the `setUpA` function is called. Should this in fact be the case? >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>>>> wrote: >>>>>> >>>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 module >>>>>>> will continue to be more or less broken for circular dependencies. >>>>>>> >>>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. <jackalmage at gmail.com >>>>>>> > wrote: >>>>>>> >>>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>>> > True, and so that's why I'm wondering if the module system can >>>>>>>> see that it >>>>>>>> > can satisfy all module requirements if it simply evaluates module >>>>>>>> C first, >>>>>>>> > followed by A or B in any order. It is easy for us humans to see >>>>>>>> that. It >>>>>>>> > would be nice for the module system to see that as well (I'm not >>>>>>>> sure if >>>>>>>> > that is spec'd or not). >>>>>>>> >>>>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>>>> module, beyond what is expressed in the `import` statements. That's >>>>>>>> assuming there's no dynamic trickery going on that would invalidate >>>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>>> >>>>>>>> Because of this, only the `import` statements are declaratively >>>>>>>> available to the module system to work with. Based on that, it >>>>>>>> definitely can't make any ordering assumptions; all it knows is >>>>>>>> that A >>>>>>>> imports C, B imports C, and C imports both A and B, making a >>>>>>>> circular >>>>>>>> import. >>>>>>>> >>>>>>>> ~TJ >>>>>>>> _______________________________________________ >>>>>>>> es-discuss mailing list >>>>>>>> es-discuss at mozilla.org >>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>> >>>>>>> >>>>>>> >>>>>> >>>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/40e0976a/attachment-0001.html>
Please note that in tc39.github.io/ecma262/#sec-moduleevaluation , Modules evaluate their children prior to evaluating themselves (15.2.1.16.5.6.c) , C should never be evaluate before A or B in this dep graph.
Please note that in https://tc39.github.io/ecma262/#sec-moduleevaluation , Modules evaluate their children prior to evaluating themselves (15.2.1.16.5.6.c) , C should never be evaluate before A or B in this dep graph. On Wed, Aug 10, 2016 at 2:41 PM, /#!/JoePea <joe at trusktr.io> wrote: > Oh! And although I think that my `setUpA` and `setUpB` technique should > work due to the fact that Webpack and Meteor load the modules in the exact > same order where the C module is executed last, this may in fact fail in > some other ES6 environment that happens to execute the C module first, in > which case `setUpA` and `setUpB` will be undefined when C is evaluated. > > So, I don't know if my solution is good. I am wondering if there's > something in the spec that guarantees that the C module evaluates last? > > */#!/*JoePea > > On Wed, Aug 10, 2016 at 12:38 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> Isaiah, also note that >> >> ```js >> export default class Foo {} >> ``` >> >> does not create a live binding that can be modified at a later point in >> time, which is the feature that my `setUpA` and `setUpB` functions are >> theoretically relying on (and which I believe the Meteor and Webpack >> environments don't handle properly if I understand live bindings correctly). >> >> */#!/*JoePea >> >> On Wed, Aug 10, 2016 at 12:31 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> In your module B, `class B` should `extends C` instead of A, so both >>> classes `A` and `B` extend from `C`. >>> >>> I made a reproduction that you can run (assuming you have Meteor >>> installed). See the following with instructions: >>> https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 >>> >>> But, anyways, the example you just gave is almost identical to my >>> [original example](https://esdiscuss.org/topic/how-to-solve-this-basic >>> -es6-module-circular-dependency-problem#content-0) (except for my `B` >>> class extends from the `C` class). >>> >>> I can make a reproduction of that too if you want, but what happens is >>> that the environment will try to execute module A before executing module >>> C, at which point `C` is `undefined` inside of module `A`. Basically, the >>> result would be the same as writing: >>> >>> ```js >>> class A extends undefined {} // throws an Error >>> ``` >>> >>> I noticed that both Meteor and Webpack will try and execute modules A >>> and B *first*, then finally C, so I thought I could export the setup >>> functions andrun them after defining the C class so that even if modules A >>> and B run first the actual class definitions would run after the C class >>> definition. You can see that I'm trying to take advantage of "live >>> bindings" in order for this to work (but it didn't, hence I have to pass C >>> into the setup functions). >>> >>> I have a feeling that both Meteor's and Webpack's implementations aren't >>> up-to-spec as far as live bindings with circular dependencies, but I could >>> be wrong. >>> >>> > You shouldn't need a `setUpA` export, especially called by one of its >>> dependencies. Just declare and initialize that crap when it's being >>> declared. >>> >>> That's what I thought at first too, but it's not the case, and I'm >>> trying to find a solution. >>> >>> */#!/*JoePea >>> >>> On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows <isiahmeadows at gmail.com> >>> wrote: >>> >>>> First of all, I'll point out that even if it's an internal API, you >>>> should just initialize them immediately. You already have an otherwise >>>> fully initialized C, so you should just add them whenever it comes. You >>>> shouldn't need a `setUpA` export, especially called by one of its >>>> dependencies. Just declare and initialize that crap when it's being >>>> declared. >>>> >>>> ```js >>>> /* index.js */ >>>> import A from './app/A' >>>> console.log('Entrypoint', A) >>>> ``` >>>> >>>> ```js >>>> /* app/A.js */ >>>> import C from './C' >>>> >>>> export default class A eclxtends C { >>>> // ... >>>> } >>>> >>>> // set up A here >>>> console.log('Module A') >>>> ``` >>>> >>>> ```js >>>> /* app/B.js */ >>>> import C from './C' >>>> >>>> export default class B extends A { >>>> // ... >>>> } >>>> >>>> // set up B here >>>> console.log('Module B') >>>> ``` >>>> >>>> ```js >>>> /* app/C.js */ >>>> import A from './A' >>>> import B from './B' >>>> >>>> export default class C { >>>> constructor() { >>>> // this may run later, after all three modules are evaluated, or >>>> // possibly never. >>>> console.log(A) >>>> console.log(B) >>>> } >>>> } >>>> >>>> // set up C >>>> console.log('Module C') >>>> ``` >>>> >>>> What's your full output, anyways? That would help me best explain >>>> what's going on, though. >>>> >>>> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> When I try this same code with Webpack, I get the *exact same >>>>> results*: the `console.log` statements in the exact same order, where the >>>>> last output shows that `A` in the entry point is `undefined`). >>>>> >>>>> Am I misunderstanding something about live bindings? Is there some >>>>> guaranteed order in which these modules should be evaluated? >>>>> >>>>> The reason why I'm after a solution for the circular dependency is >>>>> because in my real-world case I need to use `instanceof A` and `intanceof >>>>> B` within the `C` superclass defined in module C. This is a case of the >>>>> Fragile Base Class Problem where a class should usually *not* have >>>>> knowledge of it's subclasses, but the base class in my case is intended to >>>>> be internal only, not a part of the public API that end users will extend >>>>> from. >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> I can get the whole thing to work if I pass the C dependency into the >>>>>> `setUpA` and `setUpB` functions as follows, but oddly `A` is `undefined` in >>>>>> the Entrypoint module at the `console.log` statement, which makes it seem >>>>>> to me like live bindings aren't working the I was expecting. >>>>>> >>>>>> ```js >>>>>> // --- Entrypoint >>>>>> >>>>>> import A from './app/A' >>>>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module A >>>>>> >>>>>> import C from './C' >>>>>> >>>>>> let A >>>>>> >>>>>> export >>>>>> function setUpA(C) { >>>>>> >>>>>> console.log('setUpA') >>>>>> console.log(C) >>>>>> >>>>>> A = class A extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> console.log('Module A', C, setUpA) >>>>>> >>>>>> export {A as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module B >>>>>> >>>>>> import C from './C' >>>>>> >>>>>> let B >>>>>> >>>>>> export >>>>>> function setUpB(C) { >>>>>> >>>>>> console.log('setUpB', C) >>>>>> >>>>>> B = class B extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> console.log('Module B', C, setUpB) >>>>>> >>>>>> export {B as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module C >>>>>> >>>>>> import A, {setUpA} from './A' >>>>>> import B, {setUpB} from './B' >>>>>> >>>>>> let C = class C { >>>>>> constructor() { >>>>>> // this may run later, after all three modules are evaluated, >>>>>> or >>>>>> // possibly never. >>>>>> console.log(A) >>>>>> console.log(B) >>>>>> } >>>>>> } >>>>>> >>>>>> setUpA(C) >>>>>> console.log('Module C', A) >>>>>> >>>>>> setUpB(C) >>>>>> console.log('Module C', B) >>>>>> >>>>>> export {C as default} >>>>>> ``` >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>> >>>>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>>>> https://github.com/benjamn/reify)) tries to evaluate A and B first, >>>>>>> so I thought I could take advantage of "live bindings" by changing my >>>>>>> modules to the following: >>>>>>> >>>>>>> ```js >>>>>>> // --- Entrypoint >>>>>>> >>>>>>> import A from './app/A' >>>>>>> console.log('Entrypoint', A) >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module A >>>>>>> >>>>>>> import C from './C' >>>>>>> >>>>>>> let A >>>>>>> >>>>>>> export >>>>>>> function setUpA() { >>>>>>> >>>>>>> console.log('setUpA') >>>>>>> console.log(C) >>>>>>> >>>>>>> A = class A extends C { >>>>>>> // ... >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> console.log('Module A', C, setUpA) >>>>>>> >>>>>>> export {A as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module B >>>>>>> >>>>>>> import C from './C' >>>>>>> >>>>>>> let B >>>>>>> >>>>>>> export >>>>>>> function setUpB() { >>>>>>> >>>>>>> console.log('setUpB', C) >>>>>>> >>>>>>> B = class B extends C { >>>>>>> // ... >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> console.log('Module B', C, setUpB) >>>>>>> >>>>>>> export {B as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module C >>>>>>> >>>>>>> import A, {setUpA} from './A' >>>>>>> import B, {setUpB} from './B' >>>>>>> >>>>>>> let C = class C { >>>>>>> constructor() { >>>>>>> // this may run later, after all three modules are >>>>>>> evaluated, or >>>>>>> // possibly never. >>>>>>> console.log(A) >>>>>>> console.log(B) >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> setUpA() >>>>>>> console.log('Module C', A) >>>>>>> >>>>>>> setUpB() >>>>>>> console.log('Module C', B) >>>>>>> >>>>>>> export {C as default} >>>>>>> ``` >>>>>>> >>>>>>> As you can see, modules A and B simply export the code that should >>>>>>> be evaluated (note the live bindings). Then finally, the C module is >>>>>>> evaluated last. At the end of the C module, you see that it calls `setUpA` >>>>>>> and `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>>>> >>>>>>> I thought that if `C` was a live binding, then it should be ready by >>>>>>> the time the `setUpA` function is called. Should this in fact be the case? >>>>>>> >>>>>>> */#!/*JoePea >>>>>>> >>>>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>>>>> wrote: >>>>>>> >>>>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 module >>>>>>>> will continue to be more or less broken for circular dependencies. >>>>>>>> >>>>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. < >>>>>>>> jackalmage at gmail.com> wrote: >>>>>>>> >>>>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>>>> > True, and so that's why I'm wondering if the module system can >>>>>>>>> see that it >>>>>>>>> > can satisfy all module requirements if it simply evaluates >>>>>>>>> module C first, >>>>>>>>> > followed by A or B in any order. It is easy for us humans to see >>>>>>>>> that. It >>>>>>>>> > would be nice for the module system to see that as well (I'm not >>>>>>>>> sure if >>>>>>>>> > that is spec'd or not). >>>>>>>>> >>>>>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>>>>> module, beyond what is expressed in the `import` statements. >>>>>>>>> That's >>>>>>>>> assuming there's no dynamic trickery going on that would invalidate >>>>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>>>> >>>>>>>>> Because of this, only the `import` statements are declaratively >>>>>>>>> available to the module system to work with. Based on that, it >>>>>>>>> definitely can't make any ordering assumptions; all it knows is >>>>>>>>> that A >>>>>>>>> imports C, B imports C, and C imports both A and B, making a >>>>>>>>> circular >>>>>>>>> import. >>>>>>>>> >>>>>>>>> ~TJ >>>>>>>>> _______________________________________________ >>>>>>>>> es-discuss mailing list >>>>>>>>> es-discuss at mozilla.org >>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>>> >>>>>>>> >>>>>>>> >>>>>>> >>>>>> >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>> >>> >> > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/3b115b22/attachment-0001.html>
Isiah, here's the rollup.js result using my setup
functions technique. When I paste the result in my console it complains
that A is undefined inside the setUpA
function, which seems odd. Here's
the result of my original code (similar to your
example), and as you can see it will evaluate B first in which case C will
be undefined and throw an error.
Bradley, true, but C is also child of A, so it can also make sense to evaluate C before A. They are children of each other. In that case, what is the correct order of evaluation?
Isiah, here's the [rollup.js result](http://goo.gl/jl1B8H) using my setup functions technique. When I paste the result in my console it complains that A is undefined inside the `setUpA` function, which seems odd. Here's the [result of my original code](http://goo.gl/cbjVOi) (similar to your example), and as you can see it will evaluate B first in which case C will be undefined and throw an error. Bradley, true, but C is also child of A, so it can also make sense to evaluate C before A. They are children of each other. In that case, what is the correct order of evaluation? */#!/*JoePea On Wed, Aug 10, 2016 at 12:47 PM, Bradley Meck <bradley.meck at gmail.com> wrote: > Please note that in https://tc39.github.io/ecma262/#sec-moduleevaluation > , Modules evaluate their children prior to evaluating > themselves (15.2.1.16.5.6.c) , C should never be evaluate before A or B > in this dep graph. > > On Wed, Aug 10, 2016 at 2:41 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> Oh! And although I think that my `setUpA` and `setUpB` technique should >> work due to the fact that Webpack and Meteor load the modules in the exact >> same order where the C module is executed last, this may in fact fail in >> some other ES6 environment that happens to execute the C module first, in >> which case `setUpA` and `setUpB` will be undefined when C is evaluated. >> >> So, I don't know if my solution is good. I am wondering if there's >> something in the spec that guarantees that the C module evaluates last? >> >> */#!/*JoePea >> >> On Wed, Aug 10, 2016 at 12:38 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> Isaiah, also note that >>> >>> ```js >>> export default class Foo {} >>> ``` >>> >>> does not create a live binding that can be modified at a later point in >>> time, which is the feature that my `setUpA` and `setUpB` functions are >>> theoretically relying on (and which I believe the Meteor and Webpack >>> environments don't handle properly if I understand live bindings correctly). >>> >>> */#!/*JoePea >>> >>> On Wed, Aug 10, 2016 at 12:31 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> In your module B, `class B` should `extends C` instead of A, so both >>>> classes `A` and `B` extend from `C`. >>>> >>>> I made a reproduction that you can run (assuming you have Meteor >>>> installed). See the following with instructions: >>>> https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 >>>> >>>> But, anyways, the example you just gave is almost identical to my >>>> [original example](https://esdiscuss.org/topic/how-to-solve-this-basic >>>> -es6-module-circular-dependency-problem#content-0) (except for my `B` >>>> class extends from the `C` class). >>>> >>>> I can make a reproduction of that too if you want, but what happens is >>>> that the environment will try to execute module A before executing module >>>> C, at which point `C` is `undefined` inside of module `A`. Basically, the >>>> result would be the same as writing: >>>> >>>> ```js >>>> class A extends undefined {} // throws an Error >>>> ``` >>>> >>>> I noticed that both Meteor and Webpack will try and execute modules A >>>> and B *first*, then finally C, so I thought I could export the setup >>>> functions andrun them after defining the C class so that even if modules A >>>> and B run first the actual class definitions would run after the C class >>>> definition. You can see that I'm trying to take advantage of "live >>>> bindings" in order for this to work (but it didn't, hence I have to pass C >>>> into the setup functions). >>>> >>>> I have a feeling that both Meteor's and Webpack's implementations >>>> aren't up-to-spec as far as live bindings with circular dependencies, but I >>>> could be wrong. >>>> >>>> > You shouldn't need a `setUpA` export, especially called by one of its >>>> dependencies. Just declare and initialize that crap when it's being >>>> declared. >>>> >>>> That's what I thought at first too, but it's not the case, and I'm >>>> trying to find a solution. >>>> >>>> */#!/*JoePea >>>> >>>> On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows <isiahmeadows at gmail.com> >>>> wrote: >>>> >>>>> First of all, I'll point out that even if it's an internal API, you >>>>> should just initialize them immediately. You already have an otherwise >>>>> fully initialized C, so you should just add them whenever it comes. You >>>>> shouldn't need a `setUpA` export, especially called by one of its >>>>> dependencies. Just declare and initialize that crap when it's being >>>>> declared. >>>>> >>>>> ```js >>>>> /* index.js */ >>>>> import A from './app/A' >>>>> console.log('Entrypoint', A) >>>>> ``` >>>>> >>>>> ```js >>>>> /* app/A.js */ >>>>> import C from './C' >>>>> >>>>> export default class A eclxtends C { >>>>> // ... >>>>> } >>>>> >>>>> // set up A here >>>>> console.log('Module A') >>>>> ``` >>>>> >>>>> ```js >>>>> /* app/B.js */ >>>>> import C from './C' >>>>> >>>>> export default class B extends A { >>>>> // ... >>>>> } >>>>> >>>>> // set up B here >>>>> console.log('Module B') >>>>> ``` >>>>> >>>>> ```js >>>>> /* app/C.js */ >>>>> import A from './A' >>>>> import B from './B' >>>>> >>>>> export default class C { >>>>> constructor() { >>>>> // this may run later, after all three modules are evaluated, >>>>> or >>>>> // possibly never. >>>>> console.log(A) >>>>> console.log(B) >>>>> } >>>>> } >>>>> >>>>> // set up C >>>>> console.log('Module C') >>>>> ``` >>>>> >>>>> What's your full output, anyways? That would help me best explain >>>>> what's going on, though. >>>>> >>>>> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> When I try this same code with Webpack, I get the *exact same >>>>>> results*: the `console.log` statements in the exact same order, where the >>>>>> last output shows that `A` in the entry point is `undefined`). >>>>>> >>>>>> Am I misunderstanding something about live bindings? Is there some >>>>>> guaranteed order in which these modules should be evaluated? >>>>>> >>>>>> The reason why I'm after a solution for the circular dependency is >>>>>> because in my real-world case I need to use `instanceof A` and `intanceof >>>>>> B` within the `C` superclass defined in module C. This is a case of the >>>>>> Fragile Base Class Problem where a class should usually *not* have >>>>>> knowledge of it's subclasses, but the base class in my case is intended to >>>>>> be internal only, not a part of the public API that end users will extend >>>>>> from. >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>> >>>>>>> I can get the whole thing to work if I pass the C dependency into >>>>>>> the `setUpA` and `setUpB` functions as follows, but oddly `A` is >>>>>>> `undefined` in the Entrypoint module at the `console.log` statement, which >>>>>>> makes it seem to me like live bindings aren't working the I was expecting. >>>>>>> >>>>>>> ```js >>>>>>> // --- Entrypoint >>>>>>> >>>>>>> import A from './app/A' >>>>>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module A >>>>>>> >>>>>>> import C from './C' >>>>>>> >>>>>>> let A >>>>>>> >>>>>>> export >>>>>>> function setUpA(C) { >>>>>>> >>>>>>> console.log('setUpA') >>>>>>> console.log(C) >>>>>>> >>>>>>> A = class A extends C { >>>>>>> // ... >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> console.log('Module A', C, setUpA) >>>>>>> >>>>>>> export {A as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module B >>>>>>> >>>>>>> import C from './C' >>>>>>> >>>>>>> let B >>>>>>> >>>>>>> export >>>>>>> function setUpB(C) { >>>>>>> >>>>>>> console.log('setUpB', C) >>>>>>> >>>>>>> B = class B extends C { >>>>>>> // ... >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> console.log('Module B', C, setUpB) >>>>>>> >>>>>>> export {B as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module C >>>>>>> >>>>>>> import A, {setUpA} from './A' >>>>>>> import B, {setUpB} from './B' >>>>>>> >>>>>>> let C = class C { >>>>>>> constructor() { >>>>>>> // this may run later, after all three modules are >>>>>>> evaluated, or >>>>>>> // possibly never. >>>>>>> console.log(A) >>>>>>> console.log(B) >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> setUpA(C) >>>>>>> console.log('Module C', A) >>>>>>> >>>>>>> setUpB(C) >>>>>>> console.log('Module C', B) >>>>>>> >>>>>>> export {C as default} >>>>>>> ``` >>>>>>> >>>>>>> */#!/*JoePea >>>>>>> >>>>>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>> >>>>>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>>>>> https://github.com/benjamn/reify)) tries to evaluate A and B >>>>>>>> first, so I thought I could take advantage of "live bindings" by changing >>>>>>>> my modules to the following: >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Entrypoint >>>>>>>> >>>>>>>> import A from './app/A' >>>>>>>> console.log('Entrypoint', A) >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module A >>>>>>>> >>>>>>>> import C from './C' >>>>>>>> >>>>>>>> let A >>>>>>>> >>>>>>>> export >>>>>>>> function setUpA() { >>>>>>>> >>>>>>>> console.log('setUpA') >>>>>>>> console.log(C) >>>>>>>> >>>>>>>> A = class A extends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> >>>>>>>> } >>>>>>>> >>>>>>>> console.log('Module A', C, setUpA) >>>>>>>> >>>>>>>> export {A as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module B >>>>>>>> >>>>>>>> import C from './C' >>>>>>>> >>>>>>>> let B >>>>>>>> >>>>>>>> export >>>>>>>> function setUpB() { >>>>>>>> >>>>>>>> console.log('setUpB', C) >>>>>>>> >>>>>>>> B = class B extends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> >>>>>>>> } >>>>>>>> >>>>>>>> console.log('Module B', C, setUpB) >>>>>>>> >>>>>>>> export {B as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module C >>>>>>>> >>>>>>>> import A, {setUpA} from './A' >>>>>>>> import B, {setUpB} from './B' >>>>>>>> >>>>>>>> let C = class C { >>>>>>>> constructor() { >>>>>>>> // this may run later, after all three modules are >>>>>>>> evaluated, or >>>>>>>> // possibly never. >>>>>>>> console.log(A) >>>>>>>> console.log(B) >>>>>>>> } >>>>>>>> } >>>>>>>> >>>>>>>> setUpA() >>>>>>>> console.log('Module C', A) >>>>>>>> >>>>>>>> setUpB() >>>>>>>> console.log('Module C', B) >>>>>>>> >>>>>>>> export {C as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> As you can see, modules A and B simply export the code that should >>>>>>>> be evaluated (note the live bindings). Then finally, the C module is >>>>>>>> evaluated last. At the end of the C module, you see that it calls `setUpA` >>>>>>>> and `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>>>>> >>>>>>>> I thought that if `C` was a live binding, then it should be ready >>>>>>>> by the time the `setUpA` function is called. Should this in fact be the >>>>>>>> case? >>>>>>>> >>>>>>>> */#!/*JoePea >>>>>>>> >>>>>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>>>>>> wrote: >>>>>>>> >>>>>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 >>>>>>>>> module will continue to be more or less broken for circular dependencies. >>>>>>>>> >>>>>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. < >>>>>>>>> jackalmage at gmail.com> wrote: >>>>>>>>> >>>>>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> >>>>>>>>>> wrote: >>>>>>>>>> > True, and so that's why I'm wondering if the module system can >>>>>>>>>> see that it >>>>>>>>>> > can satisfy all module requirements if it simply evaluates >>>>>>>>>> module C first, >>>>>>>>>> > followed by A or B in any order. It is easy for us humans to >>>>>>>>>> see that. It >>>>>>>>>> > would be nice for the module system to see that as well (I'm >>>>>>>>>> not sure if >>>>>>>>>> > that is spec'd or not). >>>>>>>>>> >>>>>>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>>>>>> module, beyond what is expressed in the `import` statements. >>>>>>>>>> That's >>>>>>>>>> assuming there's no dynamic trickery going on that would >>>>>>>>>> invalidate >>>>>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>>>>> >>>>>>>>>> Because of this, only the `import` statements are declaratively >>>>>>>>>> available to the module system to work with. Based on that, it >>>>>>>>>> definitely can't make any ordering assumptions; all it knows is >>>>>>>>>> that A >>>>>>>>>> imports C, B imports C, and C imports both A and B, making a >>>>>>>>>> circular >>>>>>>>>> import. >>>>>>>>>> >>>>>>>>>> ~TJ >>>>>>>>>> _______________________________________________ >>>>>>>>>> es-discuss mailing list >>>>>>>>>> es-discuss at mozilla.org >>>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>> >>>>>> _______________________________________________ >>>>>> es-discuss mailing list >>>>>> es-discuss at mozilla.org >>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>> >>>>> >>>> >>> >> >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/95e16058/attachment-0001.html>
Aha, when using my setup functions, rollup.js tries to evaluate C first, in which case the hoisted functions are available, but A is not declared yet. I don't think that this would happen in real ES6 modules (no hoisting like that).
Aha, when using my setup functions, rollup.js tries to evaluate C first, in which case the *hoisted* functions are available, but A is not declared yet. I don't think that this would happen in real ES6 modules (no hoisting like that). */#!/*JoePea On Wed, Aug 10, 2016 at 12:55 PM, /#!/JoePea <joe at trusktr.io> wrote: > Isiah, here's the [rollup.js result](http://goo.gl/jl1B8H) using my setup > functions technique. When I paste the result in my console it complains > that A is undefined inside the `setUpA` function, which seems odd. Here's > the [result of my original code](http://goo.gl/cbjVOi) (similar to your > example), and as you can see it will evaluate B first in which case C will > be undefined and throw an error. > > Bradley, true, but C is also child of A, so it can also make sense to > evaluate C before A. They are children of each other. In that case, what is > the correct order of evaluation? > > */#!/*JoePea > > On Wed, Aug 10, 2016 at 12:47 PM, Bradley Meck <bradley.meck at gmail.com> > wrote: > >> Please note that in https://tc39.github.io/ecma262/#sec-moduleevaluation >> , Modules evaluate their children prior to evaluating >> themselves (15.2.1.16.5.6.c) , C should never be evaluate before A or B >> in this dep graph. >> >> On Wed, Aug 10, 2016 at 2:41 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> Oh! And although I think that my `setUpA` and `setUpB` technique should >>> work due to the fact that Webpack and Meteor load the modules in the exact >>> same order where the C module is executed last, this may in fact fail in >>> some other ES6 environment that happens to execute the C module first, in >>> which case `setUpA` and `setUpB` will be undefined when C is evaluated. >>> >>> So, I don't know if my solution is good. I am wondering if there's >>> something in the spec that guarantees that the C module evaluates last? >>> >>> */#!/*JoePea >>> >>> On Wed, Aug 10, 2016 at 12:38 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> Isaiah, also note that >>>> >>>> ```js >>>> export default class Foo {} >>>> ``` >>>> >>>> does not create a live binding that can be modified at a later point in >>>> time, which is the feature that my `setUpA` and `setUpB` functions are >>>> theoretically relying on (and which I believe the Meteor and Webpack >>>> environments don't handle properly if I understand live bindings correctly). >>>> >>>> */#!/*JoePea >>>> >>>> On Wed, Aug 10, 2016 at 12:31 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> In your module B, `class B` should `extends C` instead of A, so both >>>>> classes `A` and `B` extend from `C`. >>>>> >>>>> I made a reproduction that you can run (assuming you have Meteor >>>>> installed). See the following with instructions: >>>>> https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 >>>>> >>>>> But, anyways, the example you just gave is almost identical to my >>>>> [original example](https://esdiscuss.org/topic/how-to-solve-this-basic >>>>> -es6-module-circular-dependency-problem#content-0) (except for my `B` >>>>> class extends from the `C` class). >>>>> >>>>> I can make a reproduction of that too if you want, but what happens is >>>>> that the environment will try to execute module A before executing module >>>>> C, at which point `C` is `undefined` inside of module `A`. Basically, the >>>>> result would be the same as writing: >>>>> >>>>> ```js >>>>> class A extends undefined {} // throws an Error >>>>> ``` >>>>> >>>>> I noticed that both Meteor and Webpack will try and execute modules A >>>>> and B *first*, then finally C, so I thought I could export the setup >>>>> functions andrun them after defining the C class so that even if modules A >>>>> and B run first the actual class definitions would run after the C class >>>>> definition. You can see that I'm trying to take advantage of "live >>>>> bindings" in order for this to work (but it didn't, hence I have to pass C >>>>> into the setup functions). >>>>> >>>>> I have a feeling that both Meteor's and Webpack's implementations >>>>> aren't up-to-spec as far as live bindings with circular dependencies, but I >>>>> could be wrong. >>>>> >>>>> > You shouldn't need a `setUpA` export, especially called by one of >>>>> its dependencies. Just declare and initialize that crap when it's being >>>>> declared. >>>>> >>>>> That's what I thought at first too, but it's not the case, and I'm >>>>> trying to find a solution. >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows <isiahmeadows at gmail.com >>>>> > wrote: >>>>> >>>>>> First of all, I'll point out that even if it's an internal API, you >>>>>> should just initialize them immediately. You already have an otherwise >>>>>> fully initialized C, so you should just add them whenever it comes. You >>>>>> shouldn't need a `setUpA` export, especially called by one of its >>>>>> dependencies. Just declare and initialize that crap when it's being >>>>>> declared. >>>>>> >>>>>> ```js >>>>>> /* index.js */ >>>>>> import A from './app/A' >>>>>> console.log('Entrypoint', A) >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> /* app/A.js */ >>>>>> import C from './C' >>>>>> >>>>>> export default class A eclxtends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> // set up A here >>>>>> console.log('Module A') >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> /* app/B.js */ >>>>>> import C from './C' >>>>>> >>>>>> export default class B extends A { >>>>>> // ... >>>>>> } >>>>>> >>>>>> // set up B here >>>>>> console.log('Module B') >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> /* app/C.js */ >>>>>> import A from './A' >>>>>> import B from './B' >>>>>> >>>>>> export default class C { >>>>>> constructor() { >>>>>> // this may run later, after all three modules are evaluated, >>>>>> or >>>>>> // possibly never. >>>>>> console.log(A) >>>>>> console.log(B) >>>>>> } >>>>>> } >>>>>> >>>>>> // set up C >>>>>> console.log('Module C') >>>>>> ``` >>>>>> >>>>>> What's your full output, anyways? That would help me best explain >>>>>> what's going on, though. >>>>>> >>>>>> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >>>>>> >>>>>>> When I try this same code with Webpack, I get the *exact same >>>>>>> results*: the `console.log` statements in the exact same order, where the >>>>>>> last output shows that `A` in the entry point is `undefined`). >>>>>>> >>>>>>> Am I misunderstanding something about live bindings? Is there some >>>>>>> guaranteed order in which these modules should be evaluated? >>>>>>> >>>>>>> The reason why I'm after a solution for the circular dependency is >>>>>>> because in my real-world case I need to use `instanceof A` and `intanceof >>>>>>> B` within the `C` superclass defined in module C. This is a case of the >>>>>>> Fragile Base Class Problem where a class should usually *not* have >>>>>>> knowledge of it's subclasses, but the base class in my case is intended to >>>>>>> be internal only, not a part of the public API that end users will extend >>>>>>> from. >>>>>>> >>>>>>> */#!/*JoePea >>>>>>> >>>>>>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>> >>>>>>>> I can get the whole thing to work if I pass the C dependency into >>>>>>>> the `setUpA` and `setUpB` functions as follows, but oddly `A` is >>>>>>>> `undefined` in the Entrypoint module at the `console.log` statement, which >>>>>>>> makes it seem to me like live bindings aren't working the I was expecting. >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Entrypoint >>>>>>>> >>>>>>>> import A from './app/A' >>>>>>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint undefined" >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module A >>>>>>>> >>>>>>>> import C from './C' >>>>>>>> >>>>>>>> let A >>>>>>>> >>>>>>>> export >>>>>>>> function setUpA(C) { >>>>>>>> >>>>>>>> console.log('setUpA') >>>>>>>> console.log(C) >>>>>>>> >>>>>>>> A = class A extends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> >>>>>>>> } >>>>>>>> >>>>>>>> console.log('Module A', C, setUpA) >>>>>>>> >>>>>>>> export {A as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module B >>>>>>>> >>>>>>>> import C from './C' >>>>>>>> >>>>>>>> let B >>>>>>>> >>>>>>>> export >>>>>>>> function setUpB(C) { >>>>>>>> >>>>>>>> console.log('setUpB', C) >>>>>>>> >>>>>>>> B = class B extends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> >>>>>>>> } >>>>>>>> >>>>>>>> console.log('Module B', C, setUpB) >>>>>>>> >>>>>>>> export {B as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module C >>>>>>>> >>>>>>>> import A, {setUpA} from './A' >>>>>>>> import B, {setUpB} from './B' >>>>>>>> >>>>>>>> let C = class C { >>>>>>>> constructor() { >>>>>>>> // this may run later, after all three modules are >>>>>>>> evaluated, or >>>>>>>> // possibly never. >>>>>>>> console.log(A) >>>>>>>> console.log(B) >>>>>>>> } >>>>>>>> } >>>>>>>> >>>>>>>> setUpA(C) >>>>>>>> console.log('Module C', A) >>>>>>>> >>>>>>>> setUpB(C) >>>>>>>> console.log('Module C', B) >>>>>>>> >>>>>>>> export {C as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> */#!/*JoePea >>>>>>>> >>>>>>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>>> >>>>>>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>>>>>> https://github.com/benjamn/reify)) tries to evaluate A and B >>>>>>>>> first, so I thought I could take advantage of "live bindings" by changing >>>>>>>>> my modules to the following: >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Entrypoint >>>>>>>>> >>>>>>>>> import A from './app/A' >>>>>>>>> console.log('Entrypoint', A) >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module A >>>>>>>>> >>>>>>>>> import C from './C' >>>>>>>>> >>>>>>>>> let A >>>>>>>>> >>>>>>>>> export >>>>>>>>> function setUpA() { >>>>>>>>> >>>>>>>>> console.log('setUpA') >>>>>>>>> console.log(C) >>>>>>>>> >>>>>>>>> A = class A extends C { >>>>>>>>> // ... >>>>>>>>> } >>>>>>>>> >>>>>>>>> } >>>>>>>>> >>>>>>>>> console.log('Module A', C, setUpA) >>>>>>>>> >>>>>>>>> export {A as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module B >>>>>>>>> >>>>>>>>> import C from './C' >>>>>>>>> >>>>>>>>> let B >>>>>>>>> >>>>>>>>> export >>>>>>>>> function setUpB() { >>>>>>>>> >>>>>>>>> console.log('setUpB', C) >>>>>>>>> >>>>>>>>> B = class B extends C { >>>>>>>>> // ... >>>>>>>>> } >>>>>>>>> >>>>>>>>> } >>>>>>>>> >>>>>>>>> console.log('Module B', C, setUpB) >>>>>>>>> >>>>>>>>> export {B as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module C >>>>>>>>> >>>>>>>>> import A, {setUpA} from './A' >>>>>>>>> import B, {setUpB} from './B' >>>>>>>>> >>>>>>>>> let C = class C { >>>>>>>>> constructor() { >>>>>>>>> // this may run later, after all three modules are >>>>>>>>> evaluated, or >>>>>>>>> // possibly never. >>>>>>>>> console.log(A) >>>>>>>>> console.log(B) >>>>>>>>> } >>>>>>>>> } >>>>>>>>> >>>>>>>>> setUpA() >>>>>>>>> console.log('Module C', A) >>>>>>>>> >>>>>>>>> setUpB() >>>>>>>>> console.log('Module C', B) >>>>>>>>> >>>>>>>>> export {C as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> As you can see, modules A and B simply export the code that should >>>>>>>>> be evaluated (note the live bindings). Then finally, the C module is >>>>>>>>> evaluated last. At the end of the C module, you see that it calls `setUpA` >>>>>>>>> and `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>>>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>>>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>>>>>> >>>>>>>>> I thought that if `C` was a live binding, then it should be ready >>>>>>>>> by the time the `setUpA` function is called. Should this in fact be the >>>>>>>>> case? >>>>>>>>> >>>>>>>>> */#!/*JoePea >>>>>>>>> >>>>>>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>>>>>>> wrote: >>>>>>>>> >>>>>>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 >>>>>>>>>> module will continue to be more or less broken for circular dependencies. >>>>>>>>>> >>>>>>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. < >>>>>>>>>> jackalmage at gmail.com> wrote: >>>>>>>>>> >>>>>>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> >>>>>>>>>>> wrote: >>>>>>>>>>> > True, and so that's why I'm wondering if the module system can >>>>>>>>>>> see that it >>>>>>>>>>> > can satisfy all module requirements if it simply evaluates >>>>>>>>>>> module C first, >>>>>>>>>>> > followed by A or B in any order. It is easy for us humans to >>>>>>>>>>> see that. It >>>>>>>>>>> > would be nice for the module system to see that as well (I'm >>>>>>>>>>> not sure if >>>>>>>>>>> > that is spec'd or not). >>>>>>>>>>> >>>>>>>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>>>>>>> module, beyond what is expressed in the `import` statements. >>>>>>>>>>> That's >>>>>>>>>>> assuming there's no dynamic trickery going on that would >>>>>>>>>>> invalidate >>>>>>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>>>>>> >>>>>>>>>>> Because of this, only the `import` statements are declaratively >>>>>>>>>>> available to the module system to work with. Based on that, it >>>>>>>>>>> definitely can't make any ordering assumptions; all it knows is >>>>>>>>>>> that A >>>>>>>>>>> imports C, B imports C, and C imports both A and B, making a >>>>>>>>>>> circular >>>>>>>>>>> import. >>>>>>>>>>> >>>>>>>>>>> ~TJ >>>>>>>>>>> _______________________________________________ >>>>>>>>>>> es-discuss mailing list >>>>>>>>>>> es-discuss at mozilla.org >>>>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>>>>> >>>>>>>>>> >>>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>> _______________________________________________ >>>>>>> es-discuss mailing list >>>>>>> es-discuss at mozilla.org >>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>> >>>>>> >>>>> >>>> >>> >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/1a564101/attachment-0001.html>
---------- Forwarded message ---------- From: Bradley Meck <bradley.meck at gmail.com>
Date: Wed, Aug 10, 2016 at 3:05 PM Subject: Re: How to solve this basic ES6-module circular dependency problem? To: /#!/JoePea <joe at trusktr.io>
Bradley, true, but C is also child of A, so it can also make sense to
evaluate C before A. They are children of each other. In that case, what is the correct order of evaluation?
The graph is traversed in order of declaration in source text, depth first. trusktr/meteor/blob/issue-7621/meteor-app/client/main. js#L23 will be evaluated only after all of trusktr meteor/blob/issue-7621/meteor-app/client/main.js#L22 has been evaluated.
- A will never load via before C, because C loads A first. (confusing wording since we have circular stuff here)
- A attempting to load C when C has already begun evaluating results in the short circuit at 15.2.1.16.5.4.
---------- Forwarded message ---------- From: Bradley Meck <bradley.meck at gmail.com> Date: Wed, Aug 10, 2016 at 3:05 PM Subject: Re: How to solve this basic ES6-module circular dependency problem? To: /#!/JoePea <joe at trusktr.io> > Bradley, true, but C is also child of A, so it can also make sense to evaluate C before A. They are children of each other. In that case, what is the correct order of evaluation? The graph is traversed in order of declaration in source text, depth first. https://github.com/trusktr/meteor/blob/issue-7621/meteor-app/client/main. js#L23 will be evaluated only after all of https://github.com/trusktr/ meteor/blob/issue-7621/meteor-app/client/main.js#L22 has been evaluated. * A will never load via before C, because C loads A first. (confusing wording since we have circular stuff here) * A attempting to load C when C has already begun evaluating results in the short circuit at 15.2.1.16.5.4. On Wed, Aug 10, 2016 at 2:58 PM, /#!/JoePea <joe at trusktr.io> wrote: > Aha, when using my setup functions, rollup.js tries to evaluate C first, > in which case the *hoisted* functions are available, but A is not declared > yet. I don't think that this would happen in real ES6 modules (no hoisting > like that). > > */#!/*JoePea > > On Wed, Aug 10, 2016 at 12:55 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> Isiah, here's the [rollup.js result](http://goo.gl/jl1B8H) using my >> setup functions technique. When I paste the result in my console it >> complains that A is undefined inside the `setUpA` function, which seems >> odd. Here's the [result of my original code](http://goo.gl/cbjVOi) >> (similar to your example), and as you can see it will evaluate B first in >> which case C will be undefined and throw an error. >> >> Bradley, true, but C is also child of A, so it can also make sense to >> evaluate C before A. They are children of each other. In that case, what is >> the correct order of evaluation? >> >> */#!/*JoePea >> >> On Wed, Aug 10, 2016 at 12:47 PM, Bradley Meck <bradley.meck at gmail.com> >> wrote: >> >>> Please note that in https://tc39.github.io/ecma262/#sec-moduleevaluation >>> , Modules evaluate their children prior to evaluating >>> themselves (15.2.1.16.5.6.c) , C should never be evaluate before A or B >>> in this dep graph. >>> >>> On Wed, Aug 10, 2016 at 2:41 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> Oh! And although I think that my `setUpA` and `setUpB` technique should >>>> work due to the fact that Webpack and Meteor load the modules in the exact >>>> same order where the C module is executed last, this may in fact fail in >>>> some other ES6 environment that happens to execute the C module first, in >>>> which case `setUpA` and `setUpB` will be undefined when C is evaluated. >>>> >>>> So, I don't know if my solution is good. I am wondering if there's >>>> something in the spec that guarantees that the C module evaluates last? >>>> >>>> */#!/*JoePea >>>> >>>> On Wed, Aug 10, 2016 at 12:38 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> Isaiah, also note that >>>>> >>>>> ```js >>>>> export default class Foo {} >>>>> ``` >>>>> >>>>> does not create a live binding that can be modified at a later point >>>>> in time, which is the feature that my `setUpA` and `setUpB` functions are >>>>> theoretically relying on (and which I believe the Meteor and Webpack >>>>> environments don't handle properly if I understand live bindings correctly). >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Wed, Aug 10, 2016 at 12:31 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> In your module B, `class B` should `extends C` instead of A, so both >>>>>> classes `A` and `B` extend from `C`. >>>>>> >>>>>> I made a reproduction that you can run (assuming you have Meteor >>>>>> installed). See the following with instructions: >>>>>> https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 >>>>>> >>>>>> But, anyways, the example you just gave is almost identical to my >>>>>> [original example](https://esdiscuss.org >>>>>> /topic/how-to-solve-this-basic-es6-module-circular-dependenc >>>>>> y-problem#content-0) (except for my `B` class extends from the `C` >>>>>> class). >>>>>> >>>>>> I can make a reproduction of that too if you want, but what happens >>>>>> is that the environment will try to execute module A before executing >>>>>> module C, at which point `C` is `undefined` inside of module `A`. >>>>>> Basically, the result would be the same as writing: >>>>>> >>>>>> ```js >>>>>> class A extends undefined {} // throws an Error >>>>>> ``` >>>>>> >>>>>> I noticed that both Meteor and Webpack will try and execute modules A >>>>>> and B *first*, then finally C, so I thought I could export the setup >>>>>> functions andrun them after defining the C class so that even if modules A >>>>>> and B run first the actual class definitions would run after the C class >>>>>> definition. You can see that I'm trying to take advantage of "live >>>>>> bindings" in order for this to work (but it didn't, hence I have to pass C >>>>>> into the setup functions). >>>>>> >>>>>> I have a feeling that both Meteor's and Webpack's implementations >>>>>> aren't up-to-spec as far as live bindings with circular dependencies, but I >>>>>> could be wrong. >>>>>> >>>>>> > You shouldn't need a `setUpA` export, especially called by one of >>>>>> its dependencies. Just declare and initialize that crap when it's being >>>>>> declared. >>>>>> >>>>>> That's what I thought at first too, but it's not the case, and I'm >>>>>> trying to find a solution. >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>>> On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows < >>>>>> isiahmeadows at gmail.com> wrote: >>>>>> >>>>>>> First of all, I'll point out that even if it's an internal API, you >>>>>>> should just initialize them immediately. You already have an otherwise >>>>>>> fully initialized C, so you should just add them whenever it comes. You >>>>>>> shouldn't need a `setUpA` export, especially called by one of its >>>>>>> dependencies. Just declare and initialize that crap when it's being >>>>>>> declared. >>>>>>> >>>>>>> ```js >>>>>>> /* index.js */ >>>>>>> import A from './app/A' >>>>>>> console.log('Entrypoint', A) >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> /* app/A.js */ >>>>>>> import C from './C' >>>>>>> >>>>>>> export default class A eclxtends C { >>>>>>> // ... >>>>>>> } >>>>>>> >>>>>>> // set up A here >>>>>>> console.log('Module A') >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> /* app/B.js */ >>>>>>> import C from './C' >>>>>>> >>>>>>> export default class B extends A { >>>>>>> // ... >>>>>>> } >>>>>>> >>>>>>> // set up B here >>>>>>> console.log('Module B') >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> /* app/C.js */ >>>>>>> import A from './A' >>>>>>> import B from './B' >>>>>>> >>>>>>> export default class C { >>>>>>> constructor() { >>>>>>> // this may run later, after all three modules are >>>>>>> evaluated, or >>>>>>> // possibly never. >>>>>>> console.log(A) >>>>>>> console.log(B) >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> // set up C >>>>>>> console.log('Module C') >>>>>>> ``` >>>>>>> >>>>>>> What's your full output, anyways? That would help me best explain >>>>>>> what's going on, though. >>>>>>> >>>>>>> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >>>>>>> >>>>>>>> When I try this same code with Webpack, I get the *exact same >>>>>>>> results*: the `console.log` statements in the exact same order, where the >>>>>>>> last output shows that `A` in the entry point is `undefined`). >>>>>>>> >>>>>>>> Am I misunderstanding something about live bindings? Is there some >>>>>>>> guaranteed order in which these modules should be evaluated? >>>>>>>> >>>>>>>> The reason why I'm after a solution for the circular dependency is >>>>>>>> because in my real-world case I need to use `instanceof A` and `intanceof >>>>>>>> B` within the `C` superclass defined in module C. This is a case of the >>>>>>>> Fragile Base Class Problem where a class should usually *not* have >>>>>>>> knowledge of it's subclasses, but the base class in my case is intended to >>>>>>>> be internal only, not a part of the public API that end users will extend >>>>>>>> from. >>>>>>>> >>>>>>>> */#!/*JoePea >>>>>>>> >>>>>>>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>>> >>>>>>>>> I can get the whole thing to work if I pass the C dependency into >>>>>>>>> the `setUpA` and `setUpB` functions as follows, but oddly `A` is >>>>>>>>> `undefined` in the Entrypoint module at the `console.log` statement, which >>>>>>>>> makes it seem to me like live bindings aren't working the I was expecting. >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Entrypoint >>>>>>>>> >>>>>>>>> import A from './app/A' >>>>>>>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint >>>>>>>>> undefined" >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module A >>>>>>>>> >>>>>>>>> import C from './C' >>>>>>>>> >>>>>>>>> let A >>>>>>>>> >>>>>>>>> export >>>>>>>>> function setUpA(C) { >>>>>>>>> >>>>>>>>> console.log('setUpA') >>>>>>>>> console.log(C) >>>>>>>>> >>>>>>>>> A = class A extends C { >>>>>>>>> // ... >>>>>>>>> } >>>>>>>>> >>>>>>>>> } >>>>>>>>> >>>>>>>>> console.log('Module A', C, setUpA) >>>>>>>>> >>>>>>>>> export {A as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module B >>>>>>>>> >>>>>>>>> import C from './C' >>>>>>>>> >>>>>>>>> let B >>>>>>>>> >>>>>>>>> export >>>>>>>>> function setUpB(C) { >>>>>>>>> >>>>>>>>> console.log('setUpB', C) >>>>>>>>> >>>>>>>>> B = class B extends C { >>>>>>>>> // ... >>>>>>>>> } >>>>>>>>> >>>>>>>>> } >>>>>>>>> >>>>>>>>> console.log('Module B', C, setUpB) >>>>>>>>> >>>>>>>>> export {B as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module C >>>>>>>>> >>>>>>>>> import A, {setUpA} from './A' >>>>>>>>> import B, {setUpB} from './B' >>>>>>>>> >>>>>>>>> let C = class C { >>>>>>>>> constructor() { >>>>>>>>> // this may run later, after all three modules are >>>>>>>>> evaluated, or >>>>>>>>> // possibly never. >>>>>>>>> console.log(A) >>>>>>>>> console.log(B) >>>>>>>>> } >>>>>>>>> } >>>>>>>>> >>>>>>>>> setUpA(C) >>>>>>>>> console.log('Module C', A) >>>>>>>>> >>>>>>>>> setUpB(C) >>>>>>>>> console.log('Module C', B) >>>>>>>>> >>>>>>>>> export {C as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> */#!/*JoePea >>>>>>>>> >>>>>>>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>>>> >>>>>>>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>>>>>>> https://github.com/benjamn/reify)) tries to evaluate A and B >>>>>>>>>> first, so I thought I could take advantage of "live bindings" by changing >>>>>>>>>> my modules to the following: >>>>>>>>>> >>>>>>>>>> ```js >>>>>>>>>> // --- Entrypoint >>>>>>>>>> >>>>>>>>>> import A from './app/A' >>>>>>>>>> console.log('Entrypoint', A) >>>>>>>>>> ``` >>>>>>>>>> >>>>>>>>>> ```js >>>>>>>>>> // --- Module A >>>>>>>>>> >>>>>>>>>> import C from './C' >>>>>>>>>> >>>>>>>>>> let A >>>>>>>>>> >>>>>>>>>> export >>>>>>>>>> function setUpA() { >>>>>>>>>> >>>>>>>>>> console.log('setUpA') >>>>>>>>>> console.log(C) >>>>>>>>>> >>>>>>>>>> A = class A extends C { >>>>>>>>>> // ... >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> console.log('Module A', C, setUpA) >>>>>>>>>> >>>>>>>>>> export {A as default} >>>>>>>>>> ``` >>>>>>>>>> >>>>>>>>>> ```js >>>>>>>>>> // --- Module B >>>>>>>>>> >>>>>>>>>> import C from './C' >>>>>>>>>> >>>>>>>>>> let B >>>>>>>>>> >>>>>>>>>> export >>>>>>>>>> function setUpB() { >>>>>>>>>> >>>>>>>>>> console.log('setUpB', C) >>>>>>>>>> >>>>>>>>>> B = class B extends C { >>>>>>>>>> // ... >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> console.log('Module B', C, setUpB) >>>>>>>>>> >>>>>>>>>> export {B as default} >>>>>>>>>> ``` >>>>>>>>>> >>>>>>>>>> ```js >>>>>>>>>> // --- Module C >>>>>>>>>> >>>>>>>>>> import A, {setUpA} from './A' >>>>>>>>>> import B, {setUpB} from './B' >>>>>>>>>> >>>>>>>>>> let C = class C { >>>>>>>>>> constructor() { >>>>>>>>>> // this may run later, after all three modules are >>>>>>>>>> evaluated, or >>>>>>>>>> // possibly never. >>>>>>>>>> console.log(A) >>>>>>>>>> console.log(B) >>>>>>>>>> } >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> setUpA() >>>>>>>>>> console.log('Module C', A) >>>>>>>>>> >>>>>>>>>> setUpB() >>>>>>>>>> console.log('Module C', B) >>>>>>>>>> >>>>>>>>>> export {C as default} >>>>>>>>>> ``` >>>>>>>>>> >>>>>>>>>> As you can see, modules A and B simply export the code that >>>>>>>>>> should be evaluated (note the live bindings). Then finally, the C module is >>>>>>>>>> evaluated last. At the end of the C module, you see that it calls `setUpA` >>>>>>>>>> and `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>>>>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>>>>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>>>>>>> >>>>>>>>>> I thought that if `C` was a live binding, then it should be ready >>>>>>>>>> by the time the `setUpA` function is called. Should this in fact be the >>>>>>>>>> case? >>>>>>>>>> >>>>>>>>>> */#!/*JoePea >>>>>>>>>> >>>>>>>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com> >>>>>>>>>> wrote: >>>>>>>>>> >>>>>>>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 >>>>>>>>>>> module will continue to be more or less broken for circular dependencies. >>>>>>>>>>> >>>>>>>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. < >>>>>>>>>>> jackalmage at gmail.com> wrote: >>>>>>>>>>> >>>>>>>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> >>>>>>>>>>>> wrote: >>>>>>>>>>>> > True, and so that's why I'm wondering if the module system >>>>>>>>>>>> can see that it >>>>>>>>>>>> > can satisfy all module requirements if it simply evaluates >>>>>>>>>>>> module C first, >>>>>>>>>>>> > followed by A or B in any order. It is easy for us humans to >>>>>>>>>>>> see that. It >>>>>>>>>>>> > would be nice for the module system to see that as well (I'm >>>>>>>>>>>> not sure if >>>>>>>>>>>> > that is spec'd or not). >>>>>>>>>>>> >>>>>>>>>>>> That knowledge requires, at minimum, evaluating the rest of each >>>>>>>>>>>> module, beyond what is expressed in the `import` statements. >>>>>>>>>>>> That's >>>>>>>>>>>> assuming there's no dynamic trickery going on that would >>>>>>>>>>>> invalidate >>>>>>>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>>>>>>> >>>>>>>>>>>> Because of this, only the `import` statements are declaratively >>>>>>>>>>>> available to the module system to work with. Based on that, it >>>>>>>>>>>> definitely can't make any ordering assumptions; all it knows is >>>>>>>>>>>> that A >>>>>>>>>>>> imports C, B imports C, and C imports both A and B, making a >>>>>>>>>>>> circular >>>>>>>>>>>> import. >>>>>>>>>>>> >>>>>>>>>>>> ~TJ >>>>>>>>>>>> _______________________________________________ >>>>>>>>>>>> es-discuss mailing list >>>>>>>>>>>> es-discuss at mozilla.org >>>>>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>> >>>>>>>>> >>>>>>>> _______________________________________________ >>>>>>>> es-discuss mailing list >>>>>>>> es-discuss at mozilla.org >>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>> >>>>>>> >>>>>> >>>>> >>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>>> >>> >> > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/3acd697d/attachment-0001.html>
Bradley, thanks! Based on what you've pointed out, if the entrypoint imports only B from module B, then that should cause the import lookup to go to module C, which sees the import for A and causes the lookup to go to module A, and finally depth first executes A, then C, then finally B. This is in fact what I'm seeing in rollup.js.
Users of my API (names are more meaningful in reality, but I have three modules defining classes in exactly the same way) will only ever import A or B directly, but never C, so I should be able to use that fact to formulate a solution. I'm not sure what the solution is yet though.
Bradley, thanks! Based on what you've pointed out, if the entrypoint imports only B from module B, then that should cause the import lookup to go to module C, which sees the import for A and causes the lookup to go to module A, and finally depth first executes A, then C, then finally B. This is in fact what I'm [seeing in rollup.js](http://goo.gl/SQetDd). Users of my API (names are more meaningful in reality, but I have three modules defining classes in exactly the same way) will only ever import A or B directly, but never C, so I should be able to use that fact to formulate a solution. I'm not sure what the solution is yet though. */#!/*JoePea On Wed, Aug 10, 2016 at 1:06 PM, Bradley Meck <bradley.meck at gmail.com> wrote: > > ---------- Forwarded message ---------- > From: Bradley Meck <bradley.meck at gmail.com> > Date: Wed, Aug 10, 2016 at 3:05 PM > Subject: Re: How to solve this basic ES6-module circular dependency > problem? > To: /#!/JoePea <joe at trusktr.io> > > > > Bradley, true, but C is also child of A, so it can also make sense to > evaluate C before A. They are children of each other. In that case, what is > the correct order of evaluation? > > The graph is traversed in order of declaration in source text, depth > first. https://github.com/trusktr/meteor/blob/issue-7621/ > meteor-app/client/main.js#L23 will be evaluated only after all of > https://github.com/trusktr/meteor/blob/issue-7621/meteor- > app/client/main.js#L22 has been evaluated. > > * A will never load via before C, because C loads A first. (confusing > wording since we have circular stuff here) > * A attempting to load C when C has already begun evaluating results in > the short circuit at 15.2.1.16.5.4. > > On Wed, Aug 10, 2016 at 2:58 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> Aha, when using my setup functions, rollup.js tries to evaluate C first, >> in which case the *hoisted* functions are available, but A is not declared >> yet. I don't think that this would happen in real ES6 modules (no hoisting >> like that). >> >> */#!/*JoePea >> >> On Wed, Aug 10, 2016 at 12:55 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> Isiah, here's the [rollup.js result](http://goo.gl/jl1B8H) using my >>> setup functions technique. When I paste the result in my console it >>> complains that A is undefined inside the `setUpA` function, which seems >>> odd. Here's the [result of my original code](http://goo.gl/cbjVOi) >>> (similar to your example), and as you can see it will evaluate B first in >>> which case C will be undefined and throw an error. >>> >>> Bradley, true, but C is also child of A, so it can also make sense to >>> evaluate C before A. They are children of each other. In that case, what is >>> the correct order of evaluation? >>> >>> */#!/*JoePea >>> >>> On Wed, Aug 10, 2016 at 12:47 PM, Bradley Meck <bradley.meck at gmail.com> >>> wrote: >>> >>>> Please note that in https://tc39.github.io/ecma >>>> 262/#sec-moduleevaluation , Modules evaluate their children prior to >>>> evaluating themselves (15.2.1.16.5.6.c) , C should never be evaluate >>>> before A or B in this dep graph. >>>> >>>> On Wed, Aug 10, 2016 at 2:41 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> Oh! And although I think that my `setUpA` and `setUpB` technique >>>>> should work due to the fact that Webpack and Meteor load the modules in the >>>>> exact same order where the C module is executed last, this may in fact fail >>>>> in some other ES6 environment that happens to execute the C module first, >>>>> in which case `setUpA` and `setUpB` will be undefined when C is evaluated. >>>>> >>>>> So, I don't know if my solution is good. I am wondering if there's >>>>> something in the spec that guarantees that the C module evaluates last? >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Wed, Aug 10, 2016 at 12:38 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> Isaiah, also note that >>>>>> >>>>>> ```js >>>>>> export default class Foo {} >>>>>> ``` >>>>>> >>>>>> does not create a live binding that can be modified at a later point >>>>>> in time, which is the feature that my `setUpA` and `setUpB` functions are >>>>>> theoretically relying on (and which I believe the Meteor and Webpack >>>>>> environments don't handle properly if I understand live bindings correctly). >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>>> On Wed, Aug 10, 2016 at 12:31 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>> >>>>>>> In your module B, `class B` should `extends C` instead of A, so both >>>>>>> classes `A` and `B` extend from `C`. >>>>>>> >>>>>>> I made a reproduction that you can run (assuming you have Meteor >>>>>>> installed). See the following with instructions: >>>>>>> https://github.com/meteor/meteor/issues/7621#issuecomment-238923360 >>>>>>> >>>>>>> But, anyways, the example you just gave is almost identical to my >>>>>>> [original example](https://esdiscuss.org >>>>>>> /topic/how-to-solve-this-basic-es6-module-circular-dependenc >>>>>>> y-problem#content-0) (except for my `B` class extends from the `C` >>>>>>> class). >>>>>>> >>>>>>> I can make a reproduction of that too if you want, but what happens >>>>>>> is that the environment will try to execute module A before executing >>>>>>> module C, at which point `C` is `undefined` inside of module `A`. >>>>>>> Basically, the result would be the same as writing: >>>>>>> >>>>>>> ```js >>>>>>> class A extends undefined {} // throws an Error >>>>>>> ``` >>>>>>> >>>>>>> I noticed that both Meteor and Webpack will try and execute modules >>>>>>> A and B *first*, then finally C, so I thought I could export the setup >>>>>>> functions andrun them after defining the C class so that even if modules A >>>>>>> and B run first the actual class definitions would run after the C class >>>>>>> definition. You can see that I'm trying to take advantage of "live >>>>>>> bindings" in order for this to work (but it didn't, hence I have to pass C >>>>>>> into the setup functions). >>>>>>> >>>>>>> I have a feeling that both Meteor's and Webpack's implementations >>>>>>> aren't up-to-spec as far as live bindings with circular dependencies, but I >>>>>>> could be wrong. >>>>>>> >>>>>>> > You shouldn't need a `setUpA` export, especially called by one of >>>>>>> its dependencies. Just declare and initialize that crap when it's being >>>>>>> declared. >>>>>>> >>>>>>> That's what I thought at first too, but it's not the case, and I'm >>>>>>> trying to find a solution. >>>>>>> >>>>>>> */#!/*JoePea >>>>>>> >>>>>>> On Wed, Aug 10, 2016 at 2:41 AM, Isiah Meadows < >>>>>>> isiahmeadows at gmail.com> wrote: >>>>>>> >>>>>>>> First of all, I'll point out that even if it's an internal API, you >>>>>>>> should just initialize them immediately. You already have an otherwise >>>>>>>> fully initialized C, so you should just add them whenever it comes. You >>>>>>>> shouldn't need a `setUpA` export, especially called by one of its >>>>>>>> dependencies. Just declare and initialize that crap when it's being >>>>>>>> declared. >>>>>>>> >>>>>>>> ```js >>>>>>>> /* index.js */ >>>>>>>> import A from './app/A' >>>>>>>> console.log('Entrypoint', A) >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> /* app/A.js */ >>>>>>>> import C from './C' >>>>>>>> >>>>>>>> export default class A eclxtends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> >>>>>>>> // set up A here >>>>>>>> console.log('Module A') >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> /* app/B.js */ >>>>>>>> import C from './C' >>>>>>>> >>>>>>>> export default class B extends A { >>>>>>>> // ... >>>>>>>> } >>>>>>>> >>>>>>>> // set up B here >>>>>>>> console.log('Module B') >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> /* app/C.js */ >>>>>>>> import A from './A' >>>>>>>> import B from './B' >>>>>>>> >>>>>>>> export default class C { >>>>>>>> constructor() { >>>>>>>> // this may run later, after all three modules are >>>>>>>> evaluated, or >>>>>>>> // possibly never. >>>>>>>> console.log(A) >>>>>>>> console.log(B) >>>>>>>> } >>>>>>>> } >>>>>>>> >>>>>>>> // set up C >>>>>>>> console.log('Module C') >>>>>>>> ``` >>>>>>>> >>>>>>>> What's your full output, anyways? That would help me best explain >>>>>>>> what's going on, though. >>>>>>>> >>>>>>>> On Wed, Aug 10, 2016, 02:47 /#!/JoePea <joe at trusktr.io> wrote: >>>>>>>> >>>>>>>>> When I try this same code with Webpack, I get the *exact same >>>>>>>>> results*: the `console.log` statements in the exact same order, where the >>>>>>>>> last output shows that `A` in the entry point is `undefined`). >>>>>>>>> >>>>>>>>> Am I misunderstanding something about live bindings? Is there some >>>>>>>>> guaranteed order in which these modules should be evaluated? >>>>>>>>> >>>>>>>>> The reason why I'm after a solution for the circular dependency is >>>>>>>>> because in my real-world case I need to use `instanceof A` and `intanceof >>>>>>>>> B` within the `C` superclass defined in module C. This is a case of the >>>>>>>>> Fragile Base Class Problem where a class should usually *not* have >>>>>>>>> knowledge of it's subclasses, but the base class in my case is intended to >>>>>>>>> be internal only, not a part of the public API that end users will extend >>>>>>>>> from. >>>>>>>>> >>>>>>>>> */#!/*JoePea >>>>>>>>> >>>>>>>>> On Tue, Aug 9, 2016 at 11:12 PM, /#!/JoePea <joe at trusktr.io> >>>>>>>>> wrote: >>>>>>>>> >>>>>>>>>> I can get the whole thing to work if I pass the C dependency into >>>>>>>>>> the `setUpA` and `setUpB` functions as follows, but oddly `A` is >>>>>>>>>> `undefined` in the Entrypoint module at the `console.log` statement, which >>>>>>>>>> makes it seem to me like live bindings aren't working the I was expecting. >>>>>>>>>> >>>>>>>>>> ```js >>>>>>>>>> // --- Entrypoint >>>>>>>>>> >>>>>>>>>> import A from './app/A' >>>>>>>>>> console.log('Entrypoint', A) // HERE, output: "Entrypoint >>>>>>>>>> undefined" >>>>>>>>>> ``` >>>>>>>>>> >>>>>>>>>> ```js >>>>>>>>>> // --- Module A >>>>>>>>>> >>>>>>>>>> import C from './C' >>>>>>>>>> >>>>>>>>>> let A >>>>>>>>>> >>>>>>>>>> export >>>>>>>>>> function setUpA(C) { >>>>>>>>>> >>>>>>>>>> console.log('setUpA') >>>>>>>>>> console.log(C) >>>>>>>>>> >>>>>>>>>> A = class A extends C { >>>>>>>>>> // ... >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> console.log('Module A', C, setUpA) >>>>>>>>>> >>>>>>>>>> export {A as default} >>>>>>>>>> ``` >>>>>>>>>> >>>>>>>>>> ```js >>>>>>>>>> // --- Module B >>>>>>>>>> >>>>>>>>>> import C from './C' >>>>>>>>>> >>>>>>>>>> let B >>>>>>>>>> >>>>>>>>>> export >>>>>>>>>> function setUpB(C) { >>>>>>>>>> >>>>>>>>>> console.log('setUpB', C) >>>>>>>>>> >>>>>>>>>> B = class B extends C { >>>>>>>>>> // ... >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> console.log('Module B', C, setUpB) >>>>>>>>>> >>>>>>>>>> export {B as default} >>>>>>>>>> ``` >>>>>>>>>> >>>>>>>>>> ```js >>>>>>>>>> // --- Module C >>>>>>>>>> >>>>>>>>>> import A, {setUpA} from './A' >>>>>>>>>> import B, {setUpB} from './B' >>>>>>>>>> >>>>>>>>>> let C = class C { >>>>>>>>>> constructor() { >>>>>>>>>> // this may run later, after all three modules are >>>>>>>>>> evaluated, or >>>>>>>>>> // possibly never. >>>>>>>>>> console.log(A) >>>>>>>>>> console.log(B) >>>>>>>>>> } >>>>>>>>>> } >>>>>>>>>> >>>>>>>>>> setUpA(C) >>>>>>>>>> console.log('Module C', A) >>>>>>>>>> >>>>>>>>>> setUpB(C) >>>>>>>>>> console.log('Module C', B) >>>>>>>>>> >>>>>>>>>> export {C as default} >>>>>>>>>> ``` >>>>>>>>>> >>>>>>>>>> */#!/*JoePea >>>>>>>>>> >>>>>>>>>> On Tue, Aug 9, 2016 at 9:59 PM, /#!/JoePea <joe at trusktr.io> >>>>>>>>>> wrote: >>>>>>>>>> >>>>>>>>>>> It seems that the environment I'm in (Meteor uses [reify]( >>>>>>>>>>> https://github.com/benjamn/reify)) tries to evaluate A and B >>>>>>>>>>> first, so I thought I could take advantage of "live bindings" by changing >>>>>>>>>>> my modules to the following: >>>>>>>>>>> >>>>>>>>>>> ```js >>>>>>>>>>> // --- Entrypoint >>>>>>>>>>> >>>>>>>>>>> import A from './app/A' >>>>>>>>>>> console.log('Entrypoint', A) >>>>>>>>>>> ``` >>>>>>>>>>> >>>>>>>>>>> ```js >>>>>>>>>>> // --- Module A >>>>>>>>>>> >>>>>>>>>>> import C from './C' >>>>>>>>>>> >>>>>>>>>>> let A >>>>>>>>>>> >>>>>>>>>>> export >>>>>>>>>>> function setUpA() { >>>>>>>>>>> >>>>>>>>>>> console.log('setUpA') >>>>>>>>>>> console.log(C) >>>>>>>>>>> >>>>>>>>>>> A = class A extends C { >>>>>>>>>>> // ... >>>>>>>>>>> } >>>>>>>>>>> >>>>>>>>>>> } >>>>>>>>>>> >>>>>>>>>>> console.log('Module A', C, setUpA) >>>>>>>>>>> >>>>>>>>>>> export {A as default} >>>>>>>>>>> ``` >>>>>>>>>>> >>>>>>>>>>> ```js >>>>>>>>>>> // --- Module B >>>>>>>>>>> >>>>>>>>>>> import C from './C' >>>>>>>>>>> >>>>>>>>>>> let B >>>>>>>>>>> >>>>>>>>>>> export >>>>>>>>>>> function setUpB() { >>>>>>>>>>> >>>>>>>>>>> console.log('setUpB', C) >>>>>>>>>>> >>>>>>>>>>> B = class B extends C { >>>>>>>>>>> // ... >>>>>>>>>>> } >>>>>>>>>>> >>>>>>>>>>> } >>>>>>>>>>> >>>>>>>>>>> console.log('Module B', C, setUpB) >>>>>>>>>>> >>>>>>>>>>> export {B as default} >>>>>>>>>>> ``` >>>>>>>>>>> >>>>>>>>>>> ```js >>>>>>>>>>> // --- Module C >>>>>>>>>>> >>>>>>>>>>> import A, {setUpA} from './A' >>>>>>>>>>> import B, {setUpB} from './B' >>>>>>>>>>> >>>>>>>>>>> let C = class C { >>>>>>>>>>> constructor() { >>>>>>>>>>> // this may run later, after all three modules are >>>>>>>>>>> evaluated, or >>>>>>>>>>> // possibly never. >>>>>>>>>>> console.log(A) >>>>>>>>>>> console.log(B) >>>>>>>>>>> } >>>>>>>>>>> } >>>>>>>>>>> >>>>>>>>>>> setUpA() >>>>>>>>>>> console.log('Module C', A) >>>>>>>>>>> >>>>>>>>>>> setUpB() >>>>>>>>>>> console.log('Module C', B) >>>>>>>>>>> >>>>>>>>>>> export {C as default} >>>>>>>>>>> ``` >>>>>>>>>>> >>>>>>>>>>> As you can see, modules A and B simply export the code that >>>>>>>>>>> should be evaluated (note the live bindings). Then finally, the C module is >>>>>>>>>>> evaluated last. At the end of the C module, you see that it calls `setUpA` >>>>>>>>>>> and `setUpB`. When it fires `setUpA`, an error is thrown on the second >>>>>>>>>>> `console.log` that `C` is undefined (or, specifically, `C.default` is >>>>>>>>>>> `undefined` because the ES6 modules are compiled into CommonJS form). >>>>>>>>>>> >>>>>>>>>>> I thought that if `C` was a live binding, then it should be >>>>>>>>>>> ready by the time the `setUpA` function is called. Should this in fact be >>>>>>>>>>> the case? >>>>>>>>>>> >>>>>>>>>>> */#!/*JoePea >>>>>>>>>>> >>>>>>>>>>> On Tue, Aug 9, 2016 at 5:36 PM, John Lenz <concavelenz at gmail.com >>>>>>>>>>> > wrote: >>>>>>>>>>> >>>>>>>>>>>> Without a way to load "later" (aka "soft") dependencies, ES6 >>>>>>>>>>>> module will continue to be more or less broken for circular dependencies. >>>>>>>>>>>> >>>>>>>>>>>> On Tue, Aug 9, 2016 at 4:11 PM, Tab Atkins Jr. < >>>>>>>>>>>> jackalmage at gmail.com> wrote: >>>>>>>>>>>> >>>>>>>>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <joe at trusktr.io> >>>>>>>>>>>>> wrote: >>>>>>>>>>>>> > True, and so that's why I'm wondering if the module system >>>>>>>>>>>>> can see that it >>>>>>>>>>>>> > can satisfy all module requirements if it simply evaluates >>>>>>>>>>>>> module C first, >>>>>>>>>>>>> > followed by A or B in any order. It is easy for us humans to >>>>>>>>>>>>> see that. It >>>>>>>>>>>>> > would be nice for the module system to see that as well (I'm >>>>>>>>>>>>> not sure if >>>>>>>>>>>>> > that is spec'd or not). >>>>>>>>>>>>> >>>>>>>>>>>>> That knowledge requires, at minimum, evaluating the rest of >>>>>>>>>>>>> each >>>>>>>>>>>>> module, beyond what is expressed in the `import` statements. >>>>>>>>>>>>> That's >>>>>>>>>>>>> assuming there's no dynamic trickery going on that would >>>>>>>>>>>>> invalidate >>>>>>>>>>>>> whatever assumptions it can draw from surface-level analysis. >>>>>>>>>>>>> >>>>>>>>>>>>> Because of this, only the `import` statements are declaratively >>>>>>>>>>>>> available to the module system to work with. Based on that, it >>>>>>>>>>>>> definitely can't make any ordering assumptions; all it knows >>>>>>>>>>>>> is that A >>>>>>>>>>>>> imports C, B imports C, and C imports both A and B, making a >>>>>>>>>>>>> circular >>>>>>>>>>>>> import. >>>>>>>>>>>>> >>>>>>>>>>>>> ~TJ >>>>>>>>>>>>> _______________________________________________ >>>>>>>>>>>>> es-discuss mailing list >>>>>>>>>>>>> es-discuss at mozilla.org >>>>>>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>> >>>>>>>>> _______________________________________________ >>>>>>>>> es-discuss mailing list >>>>>>>>> es-discuss at mozilla.org >>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>>> >>>>>>>> >>>>>>> >>>>>> >>>>> >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>>> >>>> >>> >> >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> >> > > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/b2f63cb8/attachment-0001.html>
I found a solution that works in environments compiled by Babel, using the workaround suggested by Ben Newman:
// --- Module A
import C from './C'
let A = A // @benjamn's workaround applied
export
function setUpA(C) {
A = class A extends C {
// ...
}
}
export {A as default}
// --- Module B
import C from './C'
let B = B // @benjamn's workaround applied
export
function setUpB(C) {
B = class B extends C {
// ...
}
}
export {B as default}
// --- Module C
import A, {setUpA} from './A'
import B, {setUpB} from './B'
let C = class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
setUpA(C)
setUpB(C)
export {C as default}
// --- Entrypoint
import A from './A'
console.log('Entrypoint', new A) // runs the console.logs in the C
constructor.
Although that works in my environment which is compiled from ES6 modules to CommonJS by Babel, it doesn't work in Rollup.js, and may not work in other ES6 module implementations.
Is there some solution that will theoretically work in any ES6 module environment?
I found a solution that works in environments compiled by Babel, using the [workaround suggested by Ben Newman](https://github.com/ meteor/meteor/issues/7621#issuecomment-238992688): ```js // --- Module A import C from './C' let A = A // @benjamn's workaround applied export function setUpA(C) { A = class A extends C { // ... } } export {A as default} ``` ```js // --- Module B import C from './C' let B = B // @benjamn's workaround applied export function setUpB(C) { B = class B extends C { // ... } } export {B as default} ``` ```js // --- Module C import A, {setUpA} from './A' import B, {setUpB} from './B' let C = class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } setUpA(C) setUpB(C) export {C as default} ``` ```js // --- Entrypoint import A from './A' console.log('Entrypoint', new A) // runs the console.logs in the C constructor. ``` Although that works in my environment which is compiled from ES6 modules to CommonJS by Babel, it [doesn't work in Rollup.js](http://goo.gl/PXXBKI), and may not work in other ES6 module implementations. Is there some solution that will theoretically work in any ES6 module environment? */#!/*JoePea -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/07e0b7b4/attachment-0001.html>
You could also swap that around to simplify the logic in the individual classes, e.g.
// --- Module A
import C, {initC} from './c';
initC();
console.log('Module A', C)
class A extends C {
// ...
}
export {A as default}
then export that function to force-initialize the C
variable.
// --- Module C
import A from './a'
import B from './b'
var C;
export function initC(){
if (C) return;
C = class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
}
initC();
export {C as default};
So all the individual classes have to do is call the initC
function to
ensure C
has been properly initialized, then go from there. Note the
usage of var
here is also critical since let
would throw a TDZ error.
You could also swap that around to simplify the logic in the individual classes, e.g. ``` // --- Module A import C, {initC} from './c'; initC(); console.log('Module A', C) class A extends C { // ... } export {A as default} ``` then export that function to force-initialize the `C` variable. ``` // --- Module C import A from './a' import B from './b' var C; export function initC(){ if (C) return; C = class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } } initC(); export {C as default}; ``` So all the individual classes have to do is call the `initC` function to ensure `C` has been properly initialized, then go from there. Note the usage of `var` here is also critical since `let` would throw a TDZ error. On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: > I found a solution that works in environments compiled by Babel, using the > [workaround suggested by Ben Newman](https://github.com/met > eor/meteor/issues/7621#issuecomment-238992688): > > ```js > // --- Module A > > import C from './C' > > let A = A // @benjamn's workaround applied > > export > function setUpA(C) { > > A = class A extends C { > // ... > } > > } > > export {A as default} > ``` > > ```js > // --- Module B > > import C from './C' > > let B = B // @benjamn's workaround applied > > export > function setUpB(C) { > > B = class B extends C { > // ... > } > > } > > export {B as default} > ``` > > ```js > // --- Module C > > import A, {setUpA} from './A' > import B, {setUpB} from './B' > > let C = class C { > constructor() { > // this may run later, after all three modules are evaluated, or > // possibly never. > console.log(A) > console.log(B) > } > } > > setUpA(C) > setUpB(C) > > export {C as default} > ``` > > ```js > // --- Entrypoint > > import A from './A' > console.log('Entrypoint', new A) // runs the console.logs in the C > constructor. > ``` > > > Although that works in my environment which is compiled from ES6 modules > to CommonJS by Babel, it [doesn't work in Rollup.js](http://goo.gl/PXXBKI), > and may not work in other ES6 module implementations. > > Is there some solution that will theoretically work in any ES6 module > environment? > > */#!/*JoePea > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160810/849e6fff/attachment.html>
Alright, so I believe I have found the solution. It is not possible to guarantee a certain module evaluation order, but using some clever (but tedious) conditional checking I believe the problem is solved (with two caveats listed after):
// --- Entrypoint
import A from './A'
console.log('Entrypoint', new A)
// --- Module A
import C from './C'
import {setUpB} from './B'
let A
export
function setUpA(C) {
if (!A) {
A = class A extends C {
// ...
}
}
}
if (setUpA && C) setUpA(C)
if (setUpB && C) setUpB(C)
export {A as default}
// --- Module B
import C from './C'
import {setUpA} from './A'
let B
export
function setUpB(C) {
if (!B) {
B = class B extends C {
// ...
}
}
}
if (setUpA && C) setUpA(C)
if (setUpB && C) setUpB(C)
export {B as default}
// --- Module C
import A, {setUpA} from './A'
import B, {setUpB} from './B'
class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
if (setUpA && C) setUpA(C)
if (setUpB && C) setUpB(C)
export {C as default}
The caveat is that this fails in both Babel environments and in Rollup. For
it to work in Babel environments, let A
and let B
have to be changed to
let A = A
and let B = B
, as per the fault in Babel's
ES2015-to-CommonJS implementation
pointed out by Ben Newman. And it fails in Rollup because Rollup isn't
really creating live bindings,
which is fine in most cases, but doesn't work with these circular
dependencies. The Rollup output does not create the C reference before it
is ever used in the first pair of conditional checks, unlike what (I think)
would happen with real live bindings (please correct me if wrong). To
understand what I mean in the case of Rollup, just run if (FOO) console.log(FOO)
in your console, and you'll get an error because FOO is
not defined. Had the bindings been live, then FOO would be defined.
Alright, so I believe I have found the solution. It is not possible to guarantee a certain module evaluation order, but using some clever (but tedious) conditional checking I believe the problem is solved (with two caveats listed after): ```js // --- Entrypoint import A from './A' console.log('Entrypoint', new A) ``` ```js // --- Module A import C from './C' import {setUpB} from './B' let A export function setUpA(C) { if (!A) { A = class A extends C { // ... } } } if (setUpA && C) setUpA(C) if (setUpB && C) setUpB(C) export {A as default} ``` ```js // --- Module B import C from './C' import {setUpA} from './A' let B export function setUpB(C) { if (!B) { B = class B extends C { // ... } } } if (setUpA && C) setUpA(C) if (setUpB && C) setUpB(C) export {B as default} ``` ```js // --- Module C import A, {setUpA} from './A' import B, {setUpB} from './B' class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } if (setUpA && C) setUpA(C) if (setUpB && C) setUpB(C) export {C as default} ``` The caveat is that this fails in both Babel environments and in Rollup. For it to work in Babel environments, `let A` and `let B` have to be changed to `let A = A` and `let B = B`, as per the [fault in Babel's ES2015-to-CommonJS implementation]( https://github.com/meteor/meteor/issues/7621#issuecomment-238992688) pointed out by Ben Newman. And it fails in Rollup because Rollup [isn't really creating live bindings](https://github.com/rollup/rollup/issues/845), which is fine in most cases, but doesn't work with these circular dependencies. The Rollup output does not create the C reference before it is ever used in the first pair of conditional checks, unlike what (I think) would happen with real live bindings (please correct me if wrong). To understand what I mean in the case of Rollup, just run `if (FOO) console.log(FOO)` in your console, and you'll get an error because FOO is not defined. Had the bindings been live, then FOO *would* be defined. */#!/*JoePea On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: > I found a solution that works in environments compiled by Babel, using the > [workaround suggested by Ben Newman](https://github.com/met > eor/meteor/issues/7621#issuecomment-238992688): > > ```js > // --- Module A > > import C from './C' > > let A = A // @benjamn's workaround applied > > export > function setUpA(C) { > > A = class A extends C { > // ... > } > > } > > export {A as default} > ``` > > ```js > // --- Module B > > import C from './C' > > let B = B // @benjamn's workaround applied > > export > function setUpB(C) { > > B = class B extends C { > // ... > } > > } > > export {B as default} > ``` > > ```js > // --- Module C > > import A, {setUpA} from './A' > import B, {setUpB} from './B' > > let C = class C { > constructor() { > // this may run later, after all three modules are evaluated, or > // possibly never. > console.log(A) > console.log(B) > } > } > > setUpA(C) > setUpB(C) > > export {C as default} > ``` > > ```js > // --- Entrypoint > > import A from './A' > console.log('Entrypoint', new A) // runs the console.logs in the C > constructor. > ``` > > > Although that works in my environment which is compiled from ES6 modules > to CommonJS by Babel, it [doesn't work in Rollup.js](http://goo.gl/PXXBKI), > and may not work in other ES6 module implementations. > > Is there some solution that will theoretically work in any ES6 module > environment? > > */#!/*JoePea > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160811/5efae6e6/attachment.html>
Keep in mind let A = A;
is a TDZ error in any real ES6 environment.
The example I posted works properly with Babel's live-binding implementation and should require less repetition. What were your thoughts on it?
Keep in mind `let A = A;` is a TDZ error in any real ES6 environment. The example I posted works properly with Babel's live-binding implementation and should require less repetition. What were your thoughts on it? On Thu, Aug 11, 2016 at 12:23 AM, /#!/JoePea <joe at trusktr.io> wrote: > Alright, so I believe I have found the solution. It is not possible to > guarantee a certain module evaluation order, but using some clever (but > tedious) conditional checking I believe the problem is solved (with two > caveats listed after): > > ```js > // --- Entrypoint > import A from './A' > console.log('Entrypoint', new A) > ``` > > ```js > // --- Module A > > import C from './C' > import {setUpB} from './B' > > let A > > export > function setUpA(C) { > > if (!A) { > A = class A extends C { > // ... > } > } > > } > > if (setUpA && C) setUpA(C) > if (setUpB && C) setUpB(C) > > export {A as default} > ``` > > ```js > // --- Module B > > import C from './C' > import {setUpA} from './A' > > let B > > export > function setUpB(C) { > > if (!B) { > B = class B extends C { > // ... > } > } > > } > > if (setUpA && C) setUpA(C) > if (setUpB && C) setUpB(C) > > export {B as default} > ``` > > ```js > // --- Module C > > import A, {setUpA} from './A' > import B, {setUpB} from './B' > > class C { > constructor() { > // this may run later, after all three modules are evaluated, or > // possibly never. > console.log(A) > console.log(B) > } > } > > if (setUpA && C) setUpA(C) > if (setUpB && C) setUpB(C) > > export {C as default} > ``` > > The caveat is that this fails in both Babel environments and in Rollup. > For it to work in Babel environments, `let A` and `let B` have to be > changed to `let A = A` and `let B = B`, as per the [fault in Babel's > ES2015-to-CommonJS implementation](https://github.com/meteor/meteor/ > issues/7621#issuecomment-238992688) pointed out by Ben Newman. And it > fails in Rollup because Rollup [isn't really creating live bindings]( > https://github.com/rollup/rollup/issues/845), which is fine in most > cases, but doesn't work with these circular dependencies. The Rollup output > does not create the C reference before it is ever used in the first pair of > conditional checks, unlike what (I think) would happen with real live > bindings (please correct me if wrong). To understand what I mean in the > case of Rollup, just run `if (FOO) console.log(FOO)` in your console, and > you'll get an error because FOO is not defined. Had the bindings been live, > then FOO *would* be defined. > > > > */#!/*JoePea > > On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> I found a solution that works in environments compiled by Babel, using >> the [workaround suggested by Ben Newman](https://github.com/met >> eor/meteor/issues/7621#issuecomment-238992688): >> >> ```js >> // --- Module A >> >> import C from './C' >> >> let A = A // @benjamn's workaround applied >> >> export >> function setUpA(C) { >> >> A = class A extends C { >> // ... >> } >> >> } >> >> export {A as default} >> ``` >> >> ```js >> // --- Module B >> >> import C from './C' >> >> let B = B // @benjamn's workaround applied >> >> export >> function setUpB(C) { >> >> B = class B extends C { >> // ... >> } >> >> } >> >> export {B as default} >> ``` >> >> ```js >> // --- Module C >> >> import A, {setUpA} from './A' >> import B, {setUpB} from './B' >> >> let C = class C { >> constructor() { >> // this may run later, after all three modules are evaluated, or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> >> setUpA(C) >> setUpB(C) >> >> export {C as default} >> ``` >> >> ```js >> // --- Entrypoint >> >> import A from './A' >> console.log('Entrypoint', new A) // runs the console.logs in the C >> constructor. >> ``` >> >> >> Although that works in my environment which is compiled from ES6 modules >> to CommonJS by Babel, it [doesn't work in Rollup.js](http://goo.gl/PXXBKI), >> and may not work in other ES6 module implementations. >> >> Is there some solution that will theoretically work in any ES6 module >> environment? >> >> */#!/*JoePea >> > > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160811/a7529658/attachment-0001.html>
Are bindings live across all modules at the same time, as if the identifier were defined in an outer scope common to all the modules that import it?
Are bindings live across all modules at the same time, as if the identifier were defined in an outer scope common to all the modules that import it? */#!/*JoePea -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160811/4f695865/attachment.html>
Actively thinking about "live bindings" may honestly confuse the way you think about imports. When you do
export var foo = "value";
then
import {foo} from "./foo";
console.log(foo);
Every time you access foo
in the second module, you are literally
reaching into the first module to grab the current value of foo
. You can
think of it like this, in CommonJS terms:
var foo = "value";
Object.defineProperty(exports, 'foo', { get(){ return foo; } }
and
var fooMod = require('./foo');
console.log(fooMod.foo);
so accessing the 'foo' export calls a getter method to access the current
value of foo
, so any change to the value inside the first module will
immediately change the next time you trigger the getter.
Actively thinking about "live bindings" may honestly confuse the way you think about imports. When you do export var foo = "value"; then import {foo} from "./foo"; console.log(foo); Every time you access `foo` in the second module, you are literally reaching into the first module to grab the current value of `foo`. You can think of it like this, in CommonJS terms: var foo = "value"; Object.defineProperty(exports, 'foo', { get(){ return foo; } } and var fooMod = require('./foo'); console.log(fooMod.foo); so accessing the 'foo' export calls a getter method to access the current value of `foo`, so any change to the value inside the first module will immediately change the next time you trigger the getter. On Thu, Aug 11, 2016 at 5:35 PM, /#!/JoePea <joe at trusktr.io> wrote: > Are bindings live across all modules at the same time, as if the > identifier were defined in an outer scope common to all the modules that > import it? > > */#!/*JoePea > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160811/61fab17a/attachment.html>
Every time you access
foo
in the second module, you are literally
reaching into the first module to grab the current value of foo
.
Exactly! So, that means that in theory, I should be able to remove the C
parameters from my setUpA
and setUpB
functions in my last example,
so it becomes the following (but it doesn't work in some Babel environments
(Meteor, Webpack) or Rollup).
// --- Entrypoint
import A from './A'
console.log('Entrypoint', new A)
// --- Module A
import C from './C'
import {setUpB} from './B'
let A
export
function setUpA() {
if (!A) {
A = class A extends C {
// ...
}
}
}
if (setUpA && C) setUpA()
if (setUpB && C) setUpB()
export {A as default}
// --- Module B
import C from './C'
import {setUpA} from './A'
let B
export
function setUpB() {
if (!B) {
B = class B extends C {
// ...
}
}
}
if (setUpA && C) setUpA()
if (setUpB && C) setUpB()
export {B as default}
// --- Module C
import A, {setUpA} from './A'
import B, {setUpB} from './B'
class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
if (setUpA && C) setUpA()
if (setUpB && C) setUpB()
export {C as default}
I found that with Ben Newman's deferred imports idea, the problem literally goes away:
// --- Entrypoint
import A from './A'
console.log('Entrypoint', new A)
// --- Module A
import C from './C'
class A extends C {
// ...
}
export {A as default}
// --- Module B
import C from './C'
class B extends C {
// ...
}
export {B as default}
// --- Module C
class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
// reach into the A and B modules
import A from './A'
import B from './B'
console.log(A)
console.log(B)
}
}
export {C as default}
> Every time you access `foo` in the second module, you are literally reaching into the first module to grab the current value of `foo`. Exactly! So, that means that in theory, I should be able to remove the `C` parameters from my `setUpA` and `setUpB` functions in my [last example]( https://esdiscuss.org/topic/how-to-solve-this-basic-es6-module-circular-dependency-problem#content-23), so it becomes the following (but it doesn't work in some Babel environments (Meteor, Webpack) or Rollup). ```js // --- Entrypoint import A from './A' console.log('Entrypoint', new A) ``` ```js // --- Module A import C from './C' import {setUpB} from './B' let A export function setUpA() { if (!A) { A = class A extends C { // ... } } } if (setUpA && C) setUpA() if (setUpB && C) setUpB() export {A as default} ``` ```js // --- Module B import C from './C' import {setUpA} from './A' let B export function setUpB() { if (!B) { B = class B extends C { // ... } } } if (setUpA && C) setUpA() if (setUpB && C) setUpB() export {B as default} ``` ```js // --- Module C import A, {setUpA} from './A' import B, {setUpB} from './B' class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } if (setUpA && C) setUpA() if (setUpB && C) setUpB() export {C as default} ``` I found that with Ben Newman's deferred imports idea, the problem literally goes away: ```js // --- Entrypoint import A from './A' console.log('Entrypoint', new A) ``` ```js // --- Module A import C from './C' class A extends C { // ... } export {A as default} ``` ```js // --- Module B import C from './C' class B extends C { // ... } export {B as default} ``` ```js // --- Module C class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. // reach into the A and B modules import A from './A' import B from './B' console.log(A) console.log(B) } } export {C as default} ``` */#!/*JoePea -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160812/c610c4bc/attachment.html>
The example I posted works properly with Babel's live-binding
implementation and should require less repetition. What were your thoughts on it?
Your initC
solution is working in Meteor (Babel + Reify) and
Webpack+Babel, but the initC
logic seems to run twice, as if there are
two C variables instead of one. The following code is based on yours, and
the console.log('initC!!!')
statement unexpectedly executes twice, and
you'll see output like this:
module B
initC!!!
module C
initC!!!
module A
function A() { ... }
function B() { ... }
Entrypoint A {}
I'm also not sure how initC
can be defined when it is called in the B
module, which is evaluated before C and A. The evaluation order is B, C, A
(depth first). Does the initC
function get hoisted into a scope common
with all three modules? That is the only way that would seem to explain it,
but that seems to go against the intuition I had that each module had it's
own module scope (as if it were wrapped inside a function() {}
, and
therefore I thought the initC
function would be hoisted within the C
module, and that with B being evaluated first I thought an "undefined"
error would be thrown when it tried to execute initC
(but that is not
happening!). How is it that initC
can be available to the B module before
C is evaluated?
This is the code I have:
// --- Entrypoint
import A from './A'
console.log('Entrypoint', new A)
// --- Module A
import C, {initC} from './C'
console.log('module A')
initC()
class A extends C {
// ...
}
export {A as default}
// --- Module B
import C, {initC} from './C'
console.log('module B')
initC()
class B extends C {
// ...
}
export {B as default}
// --- Module C
import A from './A'
import B from './B'
console.log('module C')
let C
export function initC(){
if (C) return
console.log('initC!!!')
C = class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
}
initC()
export {C as default}
Hi Logan, > The example I posted works properly with Babel's live-binding implementation and should require less repetition. What were your thoughts on it? Your `initC` solution is working in Meteor (Babel + Reify) and Webpack+Babel, but the `initC` logic seems to run twice, as if there are two C variables instead of one. The following code is based on yours, and the `console.log('initC!!!')` statement unexpectedly executes twice, and you'll see output like this: ``` module B initC!!! module C initC!!! module A function A() { ... } function B() { ... } Entrypoint A {} ``` I'm also not sure how `initC` can be defined when it is called in the B module, which is evaluated before C and A. The evaluation order is B, C, A (depth first). Does the `initC` function get hoisted into a scope common with all three modules? That is the only way that would seem to explain it, but that seems to go against the intuition I had that each module had it's own module scope (as if it were wrapped inside a `function() {}`, and therefore I thought the `initC` function would be hoisted within the C module, and that with B being evaluated first I thought an "undefined" error would be thrown when it tried to execute `initC` (but that is not happening!). How is it that `initC` can be available to the B module before C is evaluated? This is the code I have: ```js // --- Entrypoint import A from './A' console.log('Entrypoint', new A) ``` ```js // --- Module A import C, {initC} from './C' console.log('module A') initC() class A extends C { // ... } export {A as default} ``` ```js // --- Module B import C, {initC} from './C' console.log('module B') initC() class B extends C { // ... } export {B as default} ``` ```js // --- Module C import A from './A' import B from './B' console.log('module C') let C export function initC(){ if (C) return console.log('initC!!!') C = class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } } initC() export {C as default} ``` */#!/*JoePea On Thu, Aug 11, 2016 at 10:26 AM, Logan Smyth <loganfsmyth at gmail.com> wrote: > Keep in mind `let A = A;` is a TDZ error in any real ES6 environment. > > The example I posted works properly with Babel's live-binding > implementation and should require less repetition. What were your thoughts > on it? > > On Thu, Aug 11, 2016 at 12:23 AM, /#!/JoePea <joe at trusktr.io> wrote: > >> Alright, so I believe I have found the solution. It is not possible to >> guarantee a certain module evaluation order, but using some clever (but >> tedious) conditional checking I believe the problem is solved (with two >> caveats listed after): >> >> ```js >> // --- Entrypoint >> import A from './A' >> console.log('Entrypoint', new A) >> ``` >> >> ```js >> // --- Module A >> >> import C from './C' >> import {setUpB} from './B' >> >> let A >> >> export >> function setUpA(C) { >> >> if (!A) { >> A = class A extends C { >> // ... >> } >> } >> >> } >> >> if (setUpA && C) setUpA(C) >> if (setUpB && C) setUpB(C) >> >> export {A as default} >> ``` >> >> ```js >> // --- Module B >> >> import C from './C' >> import {setUpA} from './A' >> >> let B >> >> export >> function setUpB(C) { >> >> if (!B) { >> B = class B extends C { >> // ... >> } >> } >> >> } >> >> if (setUpA && C) setUpA(C) >> if (setUpB && C) setUpB(C) >> >> export {B as default} >> ``` >> >> ```js >> // --- Module C >> >> import A, {setUpA} from './A' >> import B, {setUpB} from './B' >> >> class C { >> constructor() { >> // this may run later, after all three modules are evaluated, or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> >> if (setUpA && C) setUpA(C) >> if (setUpB && C) setUpB(C) >> >> export {C as default} >> ``` >> >> The caveat is that this fails in both Babel environments and in Rollup. >> For it to work in Babel environments, `let A` and `let B` have to be >> changed to `let A = A` and `let B = B`, as per the [fault in Babel's >> ES2015-to-CommonJS implementation](https://github >> .com/meteor/meteor/issues/7621#issuecomment-238992688) pointed out by >> Ben Newman. And it fails in Rollup because Rollup [isn't really creating >> live bindings](https://github.com/rollup/rollup/issues/845), which is >> fine in most cases, but doesn't work with these circular dependencies. The >> Rollup output does not create the C reference before it is ever used in the >> first pair of conditional checks, unlike what (I think) would happen with >> real live bindings (please correct me if wrong). To understand what I mean >> in the case of Rollup, just run `if (FOO) console.log(FOO)` in your >> console, and you'll get an error because FOO is not defined. Had the >> bindings been live, then FOO *would* be defined. >> >> >> >> */#!/*JoePea >> >> On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> I found a solution that works in environments compiled by Babel, using >>> the [workaround suggested by Ben Newman](https://github.com/met >>> eor/meteor/issues/7621#issuecomment-238992688): >>> >>> ```js >>> // --- Module A >>> >>> import C from './C' >>> >>> let A = A // @benjamn's workaround applied >>> >>> export >>> function setUpA(C) { >>> >>> A = class A extends C { >>> // ... >>> } >>> >>> } >>> >>> export {A as default} >>> ``` >>> >>> ```js >>> // --- Module B >>> >>> import C from './C' >>> >>> let B = B // @benjamn's workaround applied >>> >>> export >>> function setUpB(C) { >>> >>> B = class B extends C { >>> // ... >>> } >>> >>> } >>> >>> export {B as default} >>> ``` >>> >>> ```js >>> // --- Module C >>> >>> import A, {setUpA} from './A' >>> import B, {setUpB} from './B' >>> >>> let C = class C { >>> constructor() { >>> // this may run later, after all three modules are evaluated, or >>> // possibly never. >>> console.log(A) >>> console.log(B) >>> } >>> } >>> >>> setUpA(C) >>> setUpB(C) >>> >>> export {C as default} >>> ``` >>> >>> ```js >>> // --- Entrypoint >>> >>> import A from './A' >>> console.log('Entrypoint', new A) // runs the console.logs in the C >>> constructor. >>> ``` >>> >>> >>> Although that works in my environment which is compiled from ES6 modules >>> to CommonJS by Babel, it [doesn't work in Rollup.js](http://goo.gl/PXXBK >>> I), and may not work in other ES6 module implementations. >>> >>> Is there some solution that will theoretically work in any ES6 module >>> environment? >>> >>> */#!/*JoePea >>> >> >> >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160813/6f0c8d63/attachment-0001.html>
Your
initC
solution is working in Meteor (Babel + Reify) and
Webpack+Babel, but the initC
logic seems to run twice, as if there are
two C variables instead of one. The following code is based on yours, and
the console.log('initC!!!')
statement unexpectedly executes twice, and
you'll see output like this:
Damn, that's a Babel bug with the block scoping logic. That said, as in my
example, that needs to be var C;
anyway, let
would throw (in an
environment with working TDZ anyway). Changing it to var
also stops the
duplicate printing.
I'm also not sure how
initC
can be defined when it is called in the B
module, which is evaluated before C and A. The evaluation order is B, C, A
(depth first). Does the initC
function get hoisted into a scope common
with all three modules? That is the only way that would seem to explain it,
but that seems to go against the intuition I had that each module had it's
own module scope (as if it were wrapped inside a function() {}
, and
therefore I thought the initC
function would be hoisted within the C
module, and that with B being evaluated first I thought an "undefined"
error would be thrown when it tried to execute initC
(but that is not
happening!). How is it that initC
can be available to the B module before
C is evaluated?
There are two separate pieces to executing a module, Instantiation
, and
Evaluation
, which are what comes into play here. When you tell a JS
environment to execute a file, it will instantiate every ES6 module in
dependency graph before beginning to execute any of the modules. Babel
does its best to simulate this behavior, though it's not perfect at it. One
of the things that happens during module instantiation is that hoisted
declarations are initialized, which means you can import and call a
function declaration from any module, even if that module hasn't started
the Evaluation
phase yet, the same way it'd work with other cases of
hoisting, where execution hasn't reached the function declaration, but it
is available early.
This behavior is why you can't use let C
there, because when B
is being
evaluated, the let C
line won't have run because the Evaluation phase of
C
hasn't started yet. You can however access the initC
function because
it is a function declaration. As long as there are no TDZ errors in what
you're doing, that function can do whatever it would like, assuming it
doesn't depend on other stuff that would require Evaluation
to have
finished in C
, the same as what happens with hoisting normally. That
means for instance you couldn't do
import B from './B';
var SOME_CONSTANT = "hello";
export function initC(){
return SOME_CONSTANT;
}
because calling initC
here would return undefined
if called from inside
a dependency cycle from B
.
> Your `initC` solution is working in Meteor (Babel + Reify) and Webpack+Babel, but the `initC` logic seems to run twice, as if there are two C variables instead of one. The following code is based on yours, and the `console.log('initC!!!')` statement unexpectedly executes twice, and you'll see output like this: Damn, that's a Babel bug with the block scoping logic. That said, as in my example, that needs to be `var C;` anyway, `let` would throw (in an environment with working TDZ anyway). Changing it to `var` also stops the duplicate printing. > I'm also not sure how `initC` can be defined when it is called in the B module, which is evaluated before C and A. The evaluation order is B, C, A (depth first). Does the `initC` function get hoisted into a scope common with all three modules? That is the only way that would seem to explain it, but that seems to go against the intuition I had that each module had it's own module scope (as if it were wrapped inside a `function() {}`, and therefore I thought the `initC` function would be hoisted within the C module, and that with B being evaluated first I thought an "undefined" error would be thrown when it tried to execute `initC` (but that is not happening!). How is it that `initC` can be available to the B module before C is evaluated? There are two separate pieces to executing a module, `Instantiation`, and `Evaluation`, which are what comes into play here. When you tell a JS environment to execute a file, it will instantiate every ES6 module in dependency graph before beginning to execute _any_ of the modules. Babel does its best to simulate this behavior, though it's not perfect at it. One of the things that happens during module instantiation is that hoisted declarations are initialized, which means you can import and call a function declaration from any module, even if that module hasn't started the `Evaluation` phase yet, the same way it'd work with other cases of hoisting, where execution hasn't reached the function declaration, but it is available early. This behavior is why you can't use `let C` there, because when `B` is being evaluated, the `let C` line won't have run because the Evaluation phase of `C` hasn't started yet. You can however access the `initC` function because it is a function declaration. As long as there are no TDZ errors in what you're doing, that function can do whatever it would like, assuming it doesn't depend on other stuff that would require `Evaluation` to have finished in `C`, the same as what happens with hoisting normally. That means for instance you couldn't do import B from './B'; var SOME_CONSTANT = "hello"; export function initC(){ return SOME_CONSTANT; } because calling `initC` here would return `undefined` if called from inside a dependency cycle from `B`. On Sat, Aug 13, 2016 at 9:09 PM, /#!/JoePea <joe at trusktr.io> wrote: > Hi Logan, > > > The example I posted works properly with Babel's live-binding > implementation and should require less repetition. What were your thoughts > on it? > > Your `initC` solution is working in Meteor (Babel + Reify) and > Webpack+Babel, but the `initC` logic seems to run twice, as if there are > two C variables instead of one. The following code is based on yours, and > the `console.log('initC!!!')` statement unexpectedly executes twice, and > you'll see output like this: > > ``` > module B > initC!!! > module C > initC!!! > module A > function A() { ... } > function B() { ... } > Entrypoint A {} > ``` > > I'm also not sure how `initC` can be defined when it is called in the B > module, which is evaluated before C and A. The evaluation order is B, C, A > (depth first). Does the `initC` function get hoisted into a scope common > with all three modules? That is the only way that would seem to explain it, > but that seems to go against the intuition I had that each module had it's > own module scope (as if it were wrapped inside a `function() {}`, and > therefore I thought the `initC` function would be hoisted within the C > module, and that with B being evaluated first I thought an "undefined" > error would be thrown when it tried to execute `initC` (but that is not > happening!). How is it that `initC` can be available to the B module before > C is evaluated? > > This is the code I have: > > ```js > // --- Entrypoint > import A from './A' > console.log('Entrypoint', new A) > ``` > > ```js > // --- Module A > > import C, {initC} from './C' > > console.log('module A') > initC() > > class A extends C { > // ... > } > > export {A as default} > ``` > > ```js > // --- Module B > > import C, {initC} from './C' > > console.log('module B') > initC() > > class B extends C { > // ... > } > > export {B as default} > ``` > > ```js > // --- Module C > > import A from './A' > import B from './B' > > console.log('module C') > let C > > export function initC(){ > if (C) return > > console.log('initC!!!') > > C = class C { > constructor() { > // this may run later, after all three modules are evaluated, > or > // possibly never. > console.log(A) > console.log(B) > } > } > } > > initC() > > export {C as default} > ``` > > */#!/*JoePea > > On Thu, Aug 11, 2016 at 10:26 AM, Logan Smyth <loganfsmyth at gmail.com> > wrote: > >> Keep in mind `let A = A;` is a TDZ error in any real ES6 environment. >> >> The example I posted works properly with Babel's live-binding >> implementation and should require less repetition. What were your thoughts >> on it? >> >> On Thu, Aug 11, 2016 at 12:23 AM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> Alright, so I believe I have found the solution. It is not possible to >>> guarantee a certain module evaluation order, but using some clever (but >>> tedious) conditional checking I believe the problem is solved (with two >>> caveats listed after): >>> >>> ```js >>> // --- Entrypoint >>> import A from './A' >>> console.log('Entrypoint', new A) >>> ``` >>> >>> ```js >>> // --- Module A >>> >>> import C from './C' >>> import {setUpB} from './B' >>> >>> let A >>> >>> export >>> function setUpA(C) { >>> >>> if (!A) { >>> A = class A extends C { >>> // ... >>> } >>> } >>> >>> } >>> >>> if (setUpA && C) setUpA(C) >>> if (setUpB && C) setUpB(C) >>> >>> export {A as default} >>> ``` >>> >>> ```js >>> // --- Module B >>> >>> import C from './C' >>> import {setUpA} from './A' >>> >>> let B >>> >>> export >>> function setUpB(C) { >>> >>> if (!B) { >>> B = class B extends C { >>> // ... >>> } >>> } >>> >>> } >>> >>> if (setUpA && C) setUpA(C) >>> if (setUpB && C) setUpB(C) >>> >>> export {B as default} >>> ``` >>> >>> ```js >>> // --- Module C >>> >>> import A, {setUpA} from './A' >>> import B, {setUpB} from './B' >>> >>> class C { >>> constructor() { >>> // this may run later, after all three modules are evaluated, or >>> // possibly never. >>> console.log(A) >>> console.log(B) >>> } >>> } >>> >>> if (setUpA && C) setUpA(C) >>> if (setUpB && C) setUpB(C) >>> >>> export {C as default} >>> ``` >>> >>> The caveat is that this fails in both Babel environments and in Rollup. >>> For it to work in Babel environments, `let A` and `let B` have to be >>> changed to `let A = A` and `let B = B`, as per the [fault in Babel's >>> ES2015-to-CommonJS implementation](https://github >>> .com/meteor/meteor/issues/7621#issuecomment-238992688) pointed out by >>> Ben Newman. And it fails in Rollup because Rollup [isn't really creating >>> live bindings](https://github.com/rollup/rollup/issues/845), which is >>> fine in most cases, but doesn't work with these circular dependencies. The >>> Rollup output does not create the C reference before it is ever used in the >>> first pair of conditional checks, unlike what (I think) would happen with >>> real live bindings (please correct me if wrong). To understand what I mean >>> in the case of Rollup, just run `if (FOO) console.log(FOO)` in your >>> console, and you'll get an error because FOO is not defined. Had the >>> bindings been live, then FOO *would* be defined. >>> >>> >>> >>> */#!/*JoePea >>> >>> On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> I found a solution that works in environments compiled by Babel, using >>>> the [workaround suggested by Ben Newman](https://github.com/met >>>> eor/meteor/issues/7621#issuecomment-238992688): >>>> >>>> ```js >>>> // --- Module A >>>> >>>> import C from './C' >>>> >>>> let A = A // @benjamn's workaround applied >>>> >>>> export >>>> function setUpA(C) { >>>> >>>> A = class A extends C { >>>> // ... >>>> } >>>> >>>> } >>>> >>>> export {A as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module B >>>> >>>> import C from './C' >>>> >>>> let B = B // @benjamn's workaround applied >>>> >>>> export >>>> function setUpB(C) { >>>> >>>> B = class B extends C { >>>> // ... >>>> } >>>> >>>> } >>>> >>>> export {B as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module C >>>> >>>> import A, {setUpA} from './A' >>>> import B, {setUpB} from './B' >>>> >>>> let C = class C { >>>> constructor() { >>>> // this may run later, after all three modules are evaluated, or >>>> // possibly never. >>>> console.log(A) >>>> console.log(B) >>>> } >>>> } >>>> >>>> setUpA(C) >>>> setUpB(C) >>>> >>>> export {C as default} >>>> ``` >>>> >>>> ```js >>>> // --- Entrypoint >>>> >>>> import A from './A' >>>> console.log('Entrypoint', new A) // runs the console.logs in the C >>>> constructor. >>>> ``` >>>> >>>> >>>> Although that works in my environment which is compiled from ES6 >>>> modules to CommonJS by Babel, it [doesn't work in Rollup.js]( >>>> http://goo.gl/PXXBKI), and may not work in other ES6 module >>>> implementations. >>>> >>>> Is there some solution that will theoretically work in any ES6 module >>>> environment? >>>> >>>> */#!/*JoePea >>>> >>> >>> >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160816/ec5e353c/attachment-0001.html>
Damn, that's a Babel bug with the block scoping logic. That said, as in
my example, that needs to be var C;
anyway, let
would throw (in an
environment with working TDZ anyway). Changing it to var
also stops the
duplicate printing.
Should I open an issue there? And I think you're right about it should
throw, because it is strange that the function can be executed before the
module is ever evaluated (that seems like it should be impossible), and
therefore a TDZ error would happen because the let
line wasn't
theoretically evaluated yet.
I don't see how it is possible with var
. How is it that var
s or
function
s can be hoisted out of the module? Is that part of spec? If
so, then that is different hoisting from function-based hoisting of pre-ES6.
I was under the impression that the modules were like a function, and
hoisting would only happen inside the module. In that case, the initC
function could not possibly be available until the C
module itself was
evaluated, so I was expecting for there to be an undefined
error when
initC
was called before the C
module was evaluated.
You say that
which means you can import and call a function declaration from any
module, even if that module hasn't started the Evaluation
phase yet, the
same way it'd work with other cases of hoisting, where execution hasn't
reached the function declaration, but it is available early.
Which makes sense based on what I see happening, but it seems strange because it means that the scope of the module (as far as hoisting is concerned) is not the module itself, but rather some outer scope that wraps all the modules that import a given symbol.
You said,
the same way it'd work with other cases of hoisting, where execution
hasn't reached the function declaration, but it is available early.
Pre-ES6 "hoisting" in javascript happens on boundaries set by function
s,
and I thought that modules would be similar to function bodies, and
therefore I thought that hoisting would be limited to within a module and
did not expect hoisting to go beyond the module boundary to some scope that
encompasses multiple modules. That to me is a different type of "hoisting"
than what I know from pre-ES6 JavaScript's function-scope hoisting so it
isn't necessarily the "same way it'd work with other cases of hoisting";
there is definitely some ES6-module-specific stuff happening that is a
little different from pre-ES6 function-scope hoisting. (sidenote: I've
asked the awesome Axel of 2ality to add these useful details to his
articles.)
If I use the var
method as you proposed (which is working in my Babel
env), should I expect that method to always work in any theoretical
100%-implemented-to-spec ES6 environment, not just in Babel?
If so, then this may be one of the rare cases of "when we'd want to
actually use var
instead of let
" besides for cases when we want pre-ES6
hoisting which I think should be generally avoided in order to make code
less error-prone and easier to understand. This behavior would have been
nearly-impossible to know about without the knowledge gained from this
conversation (or from reading the spec in depth which can be difficult).
> Damn, that's a Babel bug with the block scoping logic. That said, as in my example, that needs to be `var C;` anyway, `let` would throw (in an environment with working TDZ anyway). Changing it to `var` also stops the duplicate printing. Should I open an issue there? And I think you're right about it should throw, because it is strange that the function can be executed before the module is ever evaluated (that seems like it should be impossible), and therefore a TDZ error would happen because the `let` line wasn't theoretically evaluated yet. I don't see how it is possible with `var`. How is it that `var`s or `function`s can be hoisted *out of* the module? Is that part of spec? If so, then that is different hoisting from function-based hoisting of pre-ES6. I was under the impression that the modules were like a function, and hoisting would only happen inside the module. In that case, the `initC` function could not possibly be available until the `C` module itself was evaluated, so I was expecting for there to be an `undefined` error when `initC` was called before the `C` module was evaluated. You say that > which means you can import and call a function declaration from any module, even if that module hasn't started the `Evaluation` phase yet, the same way it'd work with other cases of hoisting, where execution hasn't reached the function declaration, but it is available early. Which makes sense based on what I see happening, but it seems strange because it means that the scope of the module (as far as hoisting is concerned) is not the module itself, but rather some outer scope that wraps *all* the modules that import a given symbol. You said, > the same way it'd work with other cases of hoisting, where execution hasn't reached the function declaration, but it is available early. Pre-ES6 "hoisting" in javascript happens on boundaries set by `function`s, and I thought that modules would be similar to function bodies, and therefore I thought that hoisting would be limited to within a module and did not expect hoisting to go beyond the module boundary to some scope that encompasses multiple modules. That to me is a different type of "hoisting" than what I know from pre-ES6 JavaScript's function-scope hoisting so it isn't necessarily the "same way it'd work with other cases of hoisting"; there is definitely some ES6-module-specific stuff happening that is a little different from pre-ES6 function-scope hoisting. (sidenote: I've asked the awesome Axel of 2ality to add these useful details to his articles.) If I use the `var` method as you proposed (which is working in my Babel env), should I expect that method to always work in any theoretical 100%-implemented-to-spec ES6 environment, not just in Babel? If so, then this may be one of the rare cases of "when we'd want to actually use `var` instead of `let`" besides for cases when we want pre-ES6 hoisting which I think should be generally avoided in order to make code less error-prone and easier to understand. This behavior would have been nearly-impossible to know about without the knowledge gained from this conversation (or from reading the spec in depth which can be difficult). */#!/*JoePea On Tue, Aug 16, 2016 at 10:48 AM, Logan Smyth <loganfsmyth at gmail.com> wrote: > > Your `initC` solution is working in Meteor (Babel + Reify) and > Webpack+Babel, but the `initC` logic seems to run twice, as if there are > two C variables instead of one. The following code is based on yours, and > the `console.log('initC!!!')` statement unexpectedly executes twice, and > you'll see output like this: > > Damn, that's a Babel bug with the block scoping logic. That said, as in my > example, that needs to be `var C;` anyway, `let` would throw (in an > environment with working TDZ anyway). Changing it to `var` also stops the > duplicate printing. > > > I'm also not sure how `initC` can be defined when it is called in the B > module, which is evaluated before C and A. The evaluation order is B, C, A > (depth first). Does the `initC` function get hoisted into a scope common > with all three modules? That is the only way that would seem to explain it, > but that seems to go against the intuition I had that each module had it's > own module scope (as if it were wrapped inside a `function() {}`, and > therefore I thought the `initC` function would be hoisted within the C > module, and that with B being evaluated first I thought an "undefined" > error would be thrown when it tried to execute `initC` (but that is not > happening!). How is it that `initC` can be available to the B module before > C is evaluated? > > There are two separate pieces to executing a module, `Instantiation`, and > `Evaluation`, which are what comes into play here. When you tell a JS > environment to execute a file, it will instantiate every ES6 module in > dependency graph before beginning to execute _any_ of the modules. Babel > does its best to simulate this behavior, though it's not perfect at it. One > of the things that happens during module instantiation is that hoisted > declarations are initialized, which means you can import and call a > function declaration from any module, even if that module hasn't started > the `Evaluation` phase yet, the same way it'd work with other cases of > hoisting, where execution hasn't reached the function declaration, but it > is available early. > > This behavior is why you can't use `let C` there, because when `B` is > being evaluated, the `let C` line won't have run because the Evaluation > phase of `C` hasn't started yet. You can however access the `initC` > function because it is a function declaration. As long as there are no TDZ > errors in what you're doing, that function can do whatever it would like, > assuming it doesn't depend on other stuff that would require `Evaluation` > to have finished in `C`, the same as what happens with hoisting normally. > That means for instance you couldn't do > > import B from './B'; > var SOME_CONSTANT = "hello"; > > export function initC(){ > return SOME_CONSTANT; > } > > because calling `initC` here would return `undefined` if called from > inside a dependency cycle from `B`. > > On Sat, Aug 13, 2016 at 9:09 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> Hi Logan, >> >> > The example I posted works properly with Babel's live-binding >> implementation and should require less repetition. What were your thoughts >> on it? >> >> Your `initC` solution is working in Meteor (Babel + Reify) and >> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >> two C variables instead of one. The following code is based on yours, and >> the `console.log('initC!!!')` statement unexpectedly executes twice, and >> you'll see output like this: >> >> ``` >> module B >> initC!!! >> module C >> initC!!! >> module A >> function A() { ... } >> function B() { ... } >> Entrypoint A {} >> ``` >> >> I'm also not sure how `initC` can be defined when it is called in the B >> module, which is evaluated before C and A. The evaluation order is B, C, A >> (depth first). Does the `initC` function get hoisted into a scope common >> with all three modules? That is the only way that would seem to explain it, >> but that seems to go against the intuition I had that each module had it's >> own module scope (as if it were wrapped inside a `function() {}`, and >> therefore I thought the `initC` function would be hoisted within the C >> module, and that with B being evaluated first I thought an "undefined" >> error would be thrown when it tried to execute `initC` (but that is not >> happening!). How is it that `initC` can be available to the B module before >> C is evaluated? >> >> This is the code I have: >> >> ```js >> // --- Entrypoint >> import A from './A' >> console.log('Entrypoint', new A) >> ``` >> >> ```js >> // --- Module A >> >> import C, {initC} from './C' >> >> console.log('module A') >> initC() >> >> class A extends C { >> // ... >> } >> >> export {A as default} >> ``` >> >> ```js >> // --- Module B >> >> import C, {initC} from './C' >> >> console.log('module B') >> initC() >> >> class B extends C { >> // ... >> } >> >> export {B as default} >> ``` >> >> ```js >> // --- Module C >> >> import A from './A' >> import B from './B' >> >> console.log('module C') >> let C >> >> export function initC(){ >> if (C) return >> >> console.log('initC!!!') >> >> C = class C { >> constructor() { >> // this may run later, after all three modules are evaluated, >> or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> } >> >> initC() >> >> export {C as default} >> ``` >> >> */#!/*JoePea >> >> On Thu, Aug 11, 2016 at 10:26 AM, Logan Smyth <loganfsmyth at gmail.com> >> wrote: >> >>> Keep in mind `let A = A;` is a TDZ error in any real ES6 environment. >>> >>> The example I posted works properly with Babel's live-binding >>> implementation and should require less repetition. What were your thoughts >>> on it? >>> >>> On Thu, Aug 11, 2016 at 12:23 AM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> Alright, so I believe I have found the solution. It is not possible to >>>> guarantee a certain module evaluation order, but using some clever (but >>>> tedious) conditional checking I believe the problem is solved (with two >>>> caveats listed after): >>>> >>>> ```js >>>> // --- Entrypoint >>>> import A from './A' >>>> console.log('Entrypoint', new A) >>>> ``` >>>> >>>> ```js >>>> // --- Module A >>>> >>>> import C from './C' >>>> import {setUpB} from './B' >>>> >>>> let A >>>> >>>> export >>>> function setUpA(C) { >>>> >>>> if (!A) { >>>> A = class A extends C { >>>> // ... >>>> } >>>> } >>>> >>>> } >>>> >>>> if (setUpA && C) setUpA(C) >>>> if (setUpB && C) setUpB(C) >>>> >>>> export {A as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module B >>>> >>>> import C from './C' >>>> import {setUpA} from './A' >>>> >>>> let B >>>> >>>> export >>>> function setUpB(C) { >>>> >>>> if (!B) { >>>> B = class B extends C { >>>> // ... >>>> } >>>> } >>>> >>>> } >>>> >>>> if (setUpA && C) setUpA(C) >>>> if (setUpB && C) setUpB(C) >>>> >>>> export {B as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module C >>>> >>>> import A, {setUpA} from './A' >>>> import B, {setUpB} from './B' >>>> >>>> class C { >>>> constructor() { >>>> // this may run later, after all three modules are evaluated, or >>>> // possibly never. >>>> console.log(A) >>>> console.log(B) >>>> } >>>> } >>>> >>>> if (setUpA && C) setUpA(C) >>>> if (setUpB && C) setUpB(C) >>>> >>>> export {C as default} >>>> ``` >>>> >>>> The caveat is that this fails in both Babel environments and in Rollup. >>>> For it to work in Babel environments, `let A` and `let B` have to be >>>> changed to `let A = A` and `let B = B`, as per the [fault in Babel's >>>> ES2015-to-CommonJS implementation](https://github >>>> .com/meteor/meteor/issues/7621#issuecomment-238992688) pointed out by >>>> Ben Newman. And it fails in Rollup because Rollup [isn't really creating >>>> live bindings](https://github.com/rollup/rollup/issues/845), which is >>>> fine in most cases, but doesn't work with these circular dependencies. The >>>> Rollup output does not create the C reference before it is ever used in the >>>> first pair of conditional checks, unlike what (I think) would happen with >>>> real live bindings (please correct me if wrong). To understand what I mean >>>> in the case of Rollup, just run `if (FOO) console.log(FOO)` in your >>>> console, and you'll get an error because FOO is not defined. Had the >>>> bindings been live, then FOO *would* be defined. >>>> >>>> >>>> >>>> */#!/*JoePea >>>> >>>> On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> I found a solution that works in environments compiled by Babel, using >>>>> the [workaround suggested by Ben Newman](https://github.com/met >>>>> eor/meteor/issues/7621#issuecomment-238992688): >>>>> >>>>> ```js >>>>> // --- Module A >>>>> >>>>> import C from './C' >>>>> >>>>> let A = A // @benjamn's workaround applied >>>>> >>>>> export >>>>> function setUpA(C) { >>>>> >>>>> A = class A extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> export {A as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module B >>>>> >>>>> import C from './C' >>>>> >>>>> let B = B // @benjamn's workaround applied >>>>> >>>>> export >>>>> function setUpB(C) { >>>>> >>>>> B = class B extends C { >>>>> // ... >>>>> } >>>>> >>>>> } >>>>> >>>>> export {B as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module C >>>>> >>>>> import A, {setUpA} from './A' >>>>> import B, {setUpB} from './B' >>>>> >>>>> let C = class C { >>>>> constructor() { >>>>> // this may run later, after all three modules are evaluated, >>>>> or >>>>> // possibly never. >>>>> console.log(A) >>>>> console.log(B) >>>>> } >>>>> } >>>>> >>>>> setUpA(C) >>>>> setUpB(C) >>>>> >>>>> export {C as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Entrypoint >>>>> >>>>> import A from './A' >>>>> console.log('Entrypoint', new A) // runs the console.logs in the C >>>>> constructor. >>>>> ``` >>>>> >>>>> >>>>> Although that works in my environment which is compiled from ES6 >>>>> modules to CommonJS by Babel, it [doesn't work in Rollup.js]( >>>>> http://goo.gl/PXXBKI), and may not work in other ES6 module >>>>> implementations. >>>>> >>>>> Is there some solution that will theoretically work in any ES6 module >>>>> environment? >>>>> >>>>> */#!/*JoePea >>>>> >>>> >>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160823/90b15df8/attachment-0001.html>
Should I open an issue there? And I think you're right about it should
throw, because it is strange that the function can be executed before the
module is ever evaluated (that seems like it should be impossible), and
therefore a TDZ error would happen because the let
line wasn't
theoretically evaluated yet.
If you'd like, go for it. To note, Babel 6 doesn't implement TDZ at all at the moment anyway.
I don't see how it is possible with
var
. How is it thatvar
s orfunction
s can be hoisted out of the module? Is that part of spec? If so, then that is different hoisting from function-based hoisting of pre-ES6.
They are not hoisted out of the module. It seems like you may be misunderstanding how modules are linked together.
Pre-ES6 "hoisting" in javascript happens on boundaries set by
function
s, and I thought that modules would be similar to function bodies, and therefore I thought that hoisting would be limited to within a module and did not expect hoisting to go beyond the module boundary to some scope that encompasses multiple modules. That to me is a different type of "hoisting" than what I know from pre-ES6 JavaScript's function-scope hoisting so it isn't necessarily the "same way it'd work with other cases of hoisting"; there is definitely some ES6-module-specific stuff happening that is a little different from pre-ES6 function-scope hoisting. (sidenote: I've asked the awesome Axel of 2ality to add these useful details to his articles.)
Hoisting of var and functions behaves the same way in ES6 and in ES5 and you are correct there is no "beyond the module boundary". Let's clarify hoisting in a function.
function fn() {
console.log(inner());
function inner(){ return "hello world"; }
}
fn();
in this context, when an engine executes fn()
, before any execution has
happened inside the fn
, the engine does the following
- Create the conceptual function "scope", which exists immediately as soon as the function is called.
- Look for all function declarations inside
fn
and create their variables and assign their values to point to function objects. This is "function hoisting". - Look for all var declarations inside
fn
and create variables with the valueundefined
. This is "var hoisting". - Look for all let/const declarations, and create uninitialized variables (these will throw when accessed)
- A bunch of other stuff I'm skipping
- Execute the function body itself.
A very similar process happens for modules. You can think of it like steps 1-4 running, then before executing the module body (step 6), we recursively do this same process on every imported module. So by the time any module gets to step 6, every imported module, and every module those modules depend on, will have executed step 1-4, and have scopes that have been created, and variables that have been declared (possibly with a value, or possibly left uninitialized).
So by the time you get to step 6, there isn't a "beyond the module boundary", when you access the imported variables in your module, the JS engine will reach across the module boundary for you, to get the current value of the variable in the imported module. This behavior of reaching across module scopes is what module syntax allows, and it is what enables live binding.
Because of this live behavior, if you imported something that was defined
with let
, like your let C;
example, it would cause a TDZ error because
the variable was still "uninitialized", whereas if you make it a var
, it
will be initialized to undefined
.
If I use the
var
method as you proposed (which is working in my Babel
env), should I expect that method to always work in any theoretical 100%-implemented-to-spec ES6 environment, not just in Babel?
Correct.
> Should I open an issue there? And I think you're right about it should throw, because it is strange that the function can be executed before the module is ever evaluated (that seems like it should be impossible), and therefore a TDZ error would happen because the `let` line wasn't theoretically evaluated yet. If you'd like, go for it. To note, Babel 6 doesn't implement TDZ at all at the moment anyway. > I don't see how it is possible with `var`. How is it that `var`s or `function`s can be hoisted *out of* the module? Is that part of spec? If so, then that is different hoisting from function-based hoisting of pre-ES6. They are not hoisted out of the module. It seems like you may be misunderstanding how modules are linked together. > Pre-ES6 "hoisting" in javascript happens on boundaries set by `function`s, and I thought that modules would be similar to function bodies, and therefore I thought that hoisting would be limited to within a module and did not expect hoisting to go beyond the module boundary to some scope that encompasses multiple modules. That to me is a different type of "hoisting" than what I know from pre-ES6 JavaScript's function-scope hoisting so it isn't necessarily the "same way it'd work with other cases of hoisting"; there is definitely some ES6-module-specific stuff happening that is a little different from pre-ES6 function-scope hoisting. (sidenote: I've asked the awesome Axel of 2ality to add these useful details to his articles.) Hoisting of var and functions behaves the same way in ES6 and in ES5 and you are correct there is no "beyond the module boundary". Let's clarify hoisting in a function. ``` function fn() { console.log(inner()); function inner(){ return "hello world"; } } fn(); ``` in this context, when an engine executes `fn()`, before any execution has happened inside the `fn`, the engine does the following 1. Create the conceptual function "scope", which exists immediately as soon as the function is called. 2. Look for all function declarations inside `fn` and create their variables and assign their values to point to function objects. This is "function hoisting". 3. Look for all var declarations inside `fn` and create variables with the value `undefined`. This is "var hoisting". 4. Look for all let/const declarations, and create uninitialized variables (these will throw when accessed) 5. A bunch of other stuff I'm skipping 6. Execute the function body itself. A very similar process happens for modules. You can think of it like steps 1-4 running, then before executing the module body (step 6), we recursively do this same process on every imported module. So by the time any module gets to step 6, every imported module, and every module those modules depend on, will have executed step 1-4, and have scopes that have been created, and variables that have been declared (possibly with a value, or possibly left uninitialized). So by the time you get to step 6, there isn't a "beyond the module boundary", when you access the imported variables in your module, the JS engine will reach across the module boundary for you, to get the current value of the variable in the imported module. This behavior of reaching across module scopes is what module syntax allows, and it is what enables live binding. Because of this live behavior, if you imported something that was defined with `let`, like your `let C;` example, it would cause a TDZ error because the variable was still "uninitialized", whereas if you make it a `var`, it will be initialized to `undefined`. > If I use the `var` method as you proposed (which is working in my Babel env), should I expect that method to always work in any theoretical 100%-implemented-to-spec ES6 environment, not just in Babel? Correct. On Tue, Aug 23, 2016 at 8:17 PM, /#!/JoePea <joe at trusktr.io> wrote: > > Damn, that's a Babel bug with the block scoping logic. That said, as in > my example, that needs to be `var C;` anyway, `let` would throw (in an > environment with working TDZ anyway). Changing it to `var` also stops the > duplicate printing. > > Should I open an issue there? And I think you're right about it should > throw, because it is strange that the function can be executed before the > module is ever evaluated (that seems like it should be impossible), and > therefore a TDZ error would happen because the `let` line wasn't > theoretically evaluated yet. > > I don't see how it is possible with `var`. How is it that `var`s or > `function`s can be hoisted *out of* the module? Is that part of spec? If > so, then that is different hoisting from function-based hoisting of pre-ES6. > > I was under the impression that the modules were like a function, and > hoisting would only happen inside the module. In that case, the `initC` > function could not possibly be available until the `C` module itself was > evaluated, so I was expecting for there to be an `undefined` error when > `initC` was called before the `C` module was evaluated. > > You say that > > > which means you can import and call a function declaration from any > module, even if that module hasn't started the `Evaluation` phase yet, the > same way it'd work with other cases of hoisting, where execution hasn't > reached the function declaration, but it is available early. > > Which makes sense based on what I see happening, but it seems strange > because it means that the scope of the module (as far as hoisting is > concerned) is not the module itself, but rather some outer scope that wraps > *all* the modules that import a given symbol. > > You said, > > > the same way it'd work with other cases of hoisting, where execution > hasn't reached the function declaration, but it is available early. > > Pre-ES6 "hoisting" in javascript happens on boundaries set by `function`s, > and I thought that modules would be similar to function bodies, and > therefore I thought that hoisting would be limited to within a module and > did not expect hoisting to go beyond the module boundary to some scope that > encompasses multiple modules. That to me is a different type of "hoisting" > than what I know from pre-ES6 JavaScript's function-scope hoisting so it > isn't necessarily the "same way it'd work with other cases of hoisting"; > there is definitely some ES6-module-specific stuff happening that is a > little different from pre-ES6 function-scope hoisting. (sidenote: I've > asked the awesome Axel of 2ality to add these useful details to his > articles.) > > If I use the `var` method as you proposed (which is working in my Babel > env), should I expect that method to always work in any theoretical > 100%-implemented-to-spec ES6 environment, not just in Babel? > > If so, then this may be one of the rare cases of "when we'd want to > actually use `var` instead of `let`" besides for cases when we want pre-ES6 > hoisting which I think should be generally avoided in order to make code > less error-prone and easier to understand. This behavior would have been > nearly-impossible to know about without the knowledge gained from this > conversation (or from reading the spec in depth which can be difficult). > > > > */#!/*JoePea > > On Tue, Aug 16, 2016 at 10:48 AM, Logan Smyth <loganfsmyth at gmail.com> > wrote: > >> > Your `initC` solution is working in Meteor (Babel + Reify) and >> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >> two C variables instead of one. The following code is based on yours, and >> the `console.log('initC!!!')` statement unexpectedly executes twice, and >> you'll see output like this: >> >> Damn, that's a Babel bug with the block scoping logic. That said, as in >> my example, that needs to be `var C;` anyway, `let` would throw (in an >> environment with working TDZ anyway). Changing it to `var` also stops the >> duplicate printing. >> >> > I'm also not sure how `initC` can be defined when it is called in the >> B module, which is evaluated before C and A. The evaluation order is B, C, >> A (depth first). Does the `initC` function get hoisted into a scope common >> with all three modules? That is the only way that would seem to explain it, >> but that seems to go against the intuition I had that each module had it's >> own module scope (as if it were wrapped inside a `function() {}`, and >> therefore I thought the `initC` function would be hoisted within the C >> module, and that with B being evaluated first I thought an "undefined" >> error would be thrown when it tried to execute `initC` (but that is not >> happening!). How is it that `initC` can be available to the B module before >> C is evaluated? >> >> There are two separate pieces to executing a module, `Instantiation`, and >> `Evaluation`, which are what comes into play here. When you tell a JS >> environment to execute a file, it will instantiate every ES6 module in >> dependency graph before beginning to execute _any_ of the modules. Babel >> does its best to simulate this behavior, though it's not perfect at it. One >> of the things that happens during module instantiation is that hoisted >> declarations are initialized, which means you can import and call a >> function declaration from any module, even if that module hasn't started >> the `Evaluation` phase yet, the same way it'd work with other cases of >> hoisting, where execution hasn't reached the function declaration, but it >> is available early. >> >> This behavior is why you can't use `let C` there, because when `B` is >> being evaluated, the `let C` line won't have run because the Evaluation >> phase of `C` hasn't started yet. You can however access the `initC` >> function because it is a function declaration. As long as there are no TDZ >> errors in what you're doing, that function can do whatever it would like, >> assuming it doesn't depend on other stuff that would require `Evaluation` >> to have finished in `C`, the same as what happens with hoisting normally. >> That means for instance you couldn't do >> >> import B from './B'; >> var SOME_CONSTANT = "hello"; >> >> export function initC(){ >> return SOME_CONSTANT; >> } >> >> because calling `initC` here would return `undefined` if called from >> inside a dependency cycle from `B`. >> >> On Sat, Aug 13, 2016 at 9:09 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> Hi Logan, >>> >>> > The example I posted works properly with Babel's live-binding >>> implementation and should require less repetition. What were your thoughts >>> on it? >>> >>> Your `initC` solution is working in Meteor (Babel + Reify) and >>> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >>> two C variables instead of one. The following code is based on yours, and >>> the `console.log('initC!!!')` statement unexpectedly executes twice, and >>> you'll see output like this: >>> >>> ``` >>> module B >>> initC!!! >>> module C >>> initC!!! >>> module A >>> function A() { ... } >>> function B() { ... } >>> Entrypoint A {} >>> ``` >>> >>> I'm also not sure how `initC` can be defined when it is called in the B >>> module, which is evaluated before C and A. The evaluation order is B, C, A >>> (depth first). Does the `initC` function get hoisted into a scope common >>> with all three modules? That is the only way that would seem to explain it, >>> but that seems to go against the intuition I had that each module had it's >>> own module scope (as if it were wrapped inside a `function() {}`, and >>> therefore I thought the `initC` function would be hoisted within the C >>> module, and that with B being evaluated first I thought an "undefined" >>> error would be thrown when it tried to execute `initC` (but that is not >>> happening!). How is it that `initC` can be available to the B module before >>> C is evaluated? >>> >>> This is the code I have: >>> >>> ```js >>> // --- Entrypoint >>> import A from './A' >>> console.log('Entrypoint', new A) >>> ``` >>> >>> ```js >>> // --- Module A >>> >>> import C, {initC} from './C' >>> >>> console.log('module A') >>> initC() >>> >>> class A extends C { >>> // ... >>> } >>> >>> export {A as default} >>> ``` >>> >>> ```js >>> // --- Module B >>> >>> import C, {initC} from './C' >>> >>> console.log('module B') >>> initC() >>> >>> class B extends C { >>> // ... >>> } >>> >>> export {B as default} >>> ``` >>> >>> ```js >>> // --- Module C >>> >>> import A from './A' >>> import B from './B' >>> >>> console.log('module C') >>> let C >>> >>> export function initC(){ >>> if (C) return >>> >>> console.log('initC!!!') >>> >>> C = class C { >>> constructor() { >>> // this may run later, after all three modules are >>> evaluated, or >>> // possibly never. >>> console.log(A) >>> console.log(B) >>> } >>> } >>> } >>> >>> initC() >>> >>> export {C as default} >>> ``` >>> >>> */#!/*JoePea >>> >>> On Thu, Aug 11, 2016 at 10:26 AM, Logan Smyth <loganfsmyth at gmail.com> >>> wrote: >>> >>>> Keep in mind `let A = A;` is a TDZ error in any real ES6 environment. >>>> >>>> The example I posted works properly with Babel's live-binding >>>> implementation and should require less repetition. What were your thoughts >>>> on it? >>>> >>>> On Thu, Aug 11, 2016 at 12:23 AM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> Alright, so I believe I have found the solution. It is not possible to >>>>> guarantee a certain module evaluation order, but using some clever (but >>>>> tedious) conditional checking I believe the problem is solved (with two >>>>> caveats listed after): >>>>> >>>>> ```js >>>>> // --- Entrypoint >>>>> import A from './A' >>>>> console.log('Entrypoint', new A) >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module A >>>>> >>>>> import C from './C' >>>>> import {setUpB} from './B' >>>>> >>>>> let A >>>>> >>>>> export >>>>> function setUpA(C) { >>>>> >>>>> if (!A) { >>>>> A = class A extends C { >>>>> // ... >>>>> } >>>>> } >>>>> >>>>> } >>>>> >>>>> if (setUpA && C) setUpA(C) >>>>> if (setUpB && C) setUpB(C) >>>>> >>>>> export {A as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module B >>>>> >>>>> import C from './C' >>>>> import {setUpA} from './A' >>>>> >>>>> let B >>>>> >>>>> export >>>>> function setUpB(C) { >>>>> >>>>> if (!B) { >>>>> B = class B extends C { >>>>> // ... >>>>> } >>>>> } >>>>> >>>>> } >>>>> >>>>> if (setUpA && C) setUpA(C) >>>>> if (setUpB && C) setUpB(C) >>>>> >>>>> export {B as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module C >>>>> >>>>> import A, {setUpA} from './A' >>>>> import B, {setUpB} from './B' >>>>> >>>>> class C { >>>>> constructor() { >>>>> // this may run later, after all three modules are evaluated, >>>>> or >>>>> // possibly never. >>>>> console.log(A) >>>>> console.log(B) >>>>> } >>>>> } >>>>> >>>>> if (setUpA && C) setUpA(C) >>>>> if (setUpB && C) setUpB(C) >>>>> >>>>> export {C as default} >>>>> ``` >>>>> >>>>> The caveat is that this fails in both Babel environments and in >>>>> Rollup. For it to work in Babel environments, `let A` and `let B` have to >>>>> be changed to `let A = A` and `let B = B`, as per the [fault in Babel's >>>>> ES2015-to-CommonJS implementation](https://github >>>>> .com/meteor/meteor/issues/7621#issuecomment-238992688) pointed out by >>>>> Ben Newman. And it fails in Rollup because Rollup [isn't really creating >>>>> live bindings](https://github.com/rollup/rollup/issues/845), which is >>>>> fine in most cases, but doesn't work with these circular dependencies. The >>>>> Rollup output does not create the C reference before it is ever used in the >>>>> first pair of conditional checks, unlike what (I think) would happen with >>>>> real live bindings (please correct me if wrong). To understand what I mean >>>>> in the case of Rollup, just run `if (FOO) console.log(FOO)` in your >>>>> console, and you'll get an error because FOO is not defined. Had the >>>>> bindings been live, then FOO *would* be defined. >>>>> >>>>> >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> I found a solution that works in environments compiled by Babel, >>>>>> using the [workaround suggested by Ben Newman](https://github.com/met >>>>>> eor/meteor/issues/7621#issuecomment-238992688): >>>>>> >>>>>> ```js >>>>>> // --- Module A >>>>>> >>>>>> import C from './C' >>>>>> >>>>>> let A = A // @benjamn's workaround applied >>>>>> >>>>>> export >>>>>> function setUpA(C) { >>>>>> >>>>>> A = class A extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> export {A as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module B >>>>>> >>>>>> import C from './C' >>>>>> >>>>>> let B = B // @benjamn's workaround applied >>>>>> >>>>>> export >>>>>> function setUpB(C) { >>>>>> >>>>>> B = class B extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> export {B as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module C >>>>>> >>>>>> import A, {setUpA} from './A' >>>>>> import B, {setUpB} from './B' >>>>>> >>>>>> let C = class C { >>>>>> constructor() { >>>>>> // this may run later, after all three modules are evaluated, >>>>>> or >>>>>> // possibly never. >>>>>> console.log(A) >>>>>> console.log(B) >>>>>> } >>>>>> } >>>>>> >>>>>> setUpA(C) >>>>>> setUpB(C) >>>>>> >>>>>> export {C as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Entrypoint >>>>>> >>>>>> import A from './A' >>>>>> console.log('Entrypoint', new A) // runs the console.logs in the C >>>>>> constructor. >>>>>> ``` >>>>>> >>>>>> >>>>>> Although that works in my environment which is compiled from ES6 >>>>>> modules to CommonJS by Babel, it [doesn't work in Rollup.js]( >>>>>> http://goo.gl/PXXBKI), and may not work in other ES6 module >>>>>> implementations. >>>>>> >>>>>> Is there some solution that will theoretically work in any ES6 module >>>>>> environment? >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>> >>>>> >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>>> >>>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160823/58972455/attachment-0001.html>
Just out of curiosity; And because this is a singular issue using babel... why are you bothering to use import when nothing supports it or apparently plans to?
Why Was import designed in such a way to not be polyfillable? But instead requires a magical syntax to invoke?
Why structured reuse of modules such a bastard child?
Just out of curiosity; And because this is a singular issue using babel... why are you bothering to use import when nothing supports it or apparently plans to? Why Was import designed in such a way to not be polyfillable? But instead requires a magical syntax to invoke? Why structured reuse of modules such a bastard child? -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160823/9f0d26a8/attachment.html>
Thanks for that explanation Logan, that was very helpful!
I understand correctly, function scopes are instantiated only right before a function will run, while the module scopes are all instantiated first before any modules are evaluated, not right before the module code will be evaluated. That's where my assumption was wrong, as I was thinking that the hoisting would happen right before module evaluation (similar to functions), and therefore it would have been impossible for module B to access initC in your example, but now I get it. Thanks a ton!
Thanks for that explanation Logan, that was very helpful! I understand correctly, function scopes are instantiated only right before a function will run, while the module scopes are all instantiated first before any modules are evaluated, not right before the module code will be evaluated. That's where my assumption was wrong, as I was thinking that the hoisting would happen right before module evaluation (similar to functions), and therefore it would have been impossible for module B to access initC in your example, but now I get it. Thanks a ton! */#!/*JoePea -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160824/619d264f/attachment.html>
Hello Logan, I wonder if I can borrow your expertise for just a moment:
I ran into another problem. Suppose I have the following code which is very
similar to the code I had before (using the var
trick along with initC
function), but now Module C imports someFunction
from a newly added
module.
The problem is that module B will be evaluated before both Module C and the
utilities module. When initC
is called inside of the B module, the
initC
function will try and call someFunction
but there will be an
error because the utilities module was not yet evaluated, so someFunction
is undefined.
What would you recommend as a solution?
Here's the code:
// --- Entrypoint
import A from './A'
console.log('Entrypoint', new A)
// --- Module A
import C, {initC} from './C'
console.log('module A')
initC()
class A extends C {
// ...
}
export {A as default}
// --- Module B
import C, {initC} from './C'
console.log('module B')
initC()
class B extends C {
// ...
}
export {B as default}
// --- Module C
import A from './A'
import B from './B'
import {someFunction} from './utilities'
console.log('module C')
var C
initC()
export function initC(){
if (C) return
console.log('initC!!!')
someFunction()
C = class C {
constructor() {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A)
console.log(B)
}
}
}
export {C as default}
// utilities.js
var someFunction = () => {...}
export {
someFunction,
}
I think a possible (though strange) solution is to change the import order in Module C, f.e.:
import A from './A'
import B from './B'
import {someFunction} from './utilities'
I haven't actually tested that example code, but I believe I'm having a problem like this with Webpack+Babel here: trusktr/infamous/tree/circular-dep-bug
In this case, there is a circular dependency between src/motor/Scene
and
src/motor/Sizeable
(which I've already planned to eliminate, but am still
curious about how I would solve it without eliminating the circular dep).
The modules import the defaults of each other, and I'm using the var Sizeable/initSizeable
trick. However, there's a third module, Utility.js
from which the Sizeable
module needs to import something to use inside of
the initSizeable
function.
You can experience the error by simply opening motor-scratch.html
in your
browser, in which case you'll get an error like
Sizeable.js:328: Uncaught TypeError: Cannot read property '
makeLowercaseSetterAliases' of undefined
where makeLowercaseSetterAliases
is akin to the someFunction
of my
earlier example. The odd thing is that everything works fine in Meteor's
Reify environment, and I'm only getting this error when using the bundle
made by Webpack+Babel.
If I re-order the dependencies in src/motor/Sizeable
so that they look
like this:
import { makeLowercaseSetterAliases } from './Utility'
import XYZValues from './XYZValues'
import Motor from './Motor'
import Scene from './Scene'
then the error goes away, but I get another error:
node.js:129: Uncaught TypeError: Cannot read property 'prototype' of
undefined
I have a feeling that moving the import to the beginning solved the problem, and that now I have another problem...
If you'd like to test changes (f.e. moving the import statement), you can simply:
npm install
npm run watch
which will automatically compile the global.js file after making changes to
anything in src
, and you can reload the page in your browser.
Any and all input you may have on this would be greatly appreciated!
Thanks a ton!
All the best,
Hello Logan, I wonder if I can borrow your expertise for just a moment: I ran into another problem. Suppose I have the following code which is very similar to the code I had before (using the `var` trick along with `initC` function), but now Module C imports `someFunction` from a newly added module. The problem is that module B will be evaluated before both Module C and the utilities module. When `initC` is called inside of the B module, the `initC` function will try and call `someFunction` but there will be an error because the utilities module was not yet evaluated, so `someFunction` is undefined. What would you recommend as a solution? Here's the code: ```js // --- Entrypoint import A from './A' console.log('Entrypoint', new A) ``` ```js // --- Module A import C, {initC} from './C' console.log('module A') initC() class A extends C { // ... } export {A as default} ``` ```js // --- Module B import C, {initC} from './C' console.log('module B') initC() class B extends C { // ... } export {B as default} ``` ```js // --- Module C import A from './A' import B from './B' import {someFunction} from './utilities' console.log('module C') var C initC() export function initC(){ if (C) return console.log('initC!!!') someFunction() C = class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } } } export {C as default} ``` ```js // utilities.js var someFunction = () => {...} export { someFunction, } ``` I think a possible (though strange) solution is to change the import order in Module C, f.e.: ```js import A from './A' import B from './B' import {someFunction} from './utilities' ``` I haven't actually tested that example code, but I believe I'm having a problem like this with Webpack+Babel here: https://github.com/trusktr/infamous/tree/circular-dep-bug In this case, there is a circular dependency between `src/motor/Scene` and `src/motor/Sizeable` (which I've already planned to eliminate, but am still curious about how I would solve it without eliminating the circular dep). The modules import the defaults of each other, and I'm using the `var Sizeable/initSizeable` trick. However, there's a third module, `Utility.js` from which the `Sizeable` module needs to import something to use inside of the `initSizeable` function. You can experience the error by simply opening `motor-scratch.html` in your browser, in which case you'll get an error like ``` Sizeable.js:328: Uncaught TypeError: Cannot read property ' makeLowercaseSetterAliases' of undefined ``` where `makeLowercaseSetterAliases` is akin to the `someFunction` of my earlier example. The odd thing is that everything works fine in Meteor's Reify environment, and I'm only getting this error when using the bundle made by Webpack+Babel. If I re-order the dependencies in `src/motor/Sizeable` so that they look like this: ```js import { makeLowercaseSetterAliases } from './Utility' import XYZValues from './XYZValues' import Motor from './Motor' import Scene from './Scene' ``` then the error goes away, but I get another error: ``` node.js:129: Uncaught TypeError: Cannot read property 'prototype' of undefined ``` I have a feeling that moving the import to the beginning solved the problem, and that now I have another problem... If you'd like to test changes (f.e. moving the import statement), you can simply: ``` npm install npm run watch ``` which will automatically compile the global.js file after making changes to anything in `src`, and you can reload the page in your browser. Any and all input you may have on this would be greatly appreciated! Thanks a ton! All the best, - Joe */#!/*JoePea On Tue, Aug 23, 2016 at 8:55 PM, Logan Smyth <loganfsmyth at gmail.com> wrote: > > Should I open an issue there? And I think you're right about it should > throw, because it is strange that the function can be executed before the > module is ever evaluated (that seems like it should be impossible), and > therefore a TDZ error would happen because the `let` line wasn't > theoretically evaluated yet. > > If you'd like, go for it. To note, Babel 6 doesn't implement TDZ at all at > the moment anyway. > > > I don't see how it is possible with `var`. How is it that `var`s or > `function`s can be hoisted *out of* the module? Is that part of spec? If > so, then that is different hoisting from function-based hoisting of pre-ES6. > > They are not hoisted out of the module. It seems like you may be > misunderstanding how modules are linked together. > > > Pre-ES6 "hoisting" in javascript happens on boundaries set by > `function`s, and I thought that modules would be similar to function > bodies, and therefore I thought that hoisting would be limited to within a > module and did not expect hoisting to go beyond the module boundary to some > scope that encompasses multiple modules. That to me is a different type of > "hoisting" than what I know from pre-ES6 JavaScript's function-scope > hoisting so it isn't necessarily the "same way it'd work with other cases > of hoisting"; there is definitely some ES6-module-specific stuff happening > that is a little different from pre-ES6 function-scope hoisting. (sidenote: > I've asked the awesome Axel of 2ality to add these useful details to his > articles.) > > Hoisting of var and functions behaves the same way in ES6 and in ES5 and > you are correct there is no "beyond the module boundary". Let's clarify > hoisting in a function. > > ``` > function fn() { > console.log(inner()); > > function inner(){ return "hello world"; } > } > fn(); > ``` > in this context, when an engine executes `fn()`, before any execution has > happened inside the `fn`, the engine does the following > > 1. Create the conceptual function "scope", which exists immediately as > soon as the function is called. > 2. Look for all function declarations inside `fn` and create their > variables and assign their values to point to function objects. This is > "function hoisting". > 3. Look for all var declarations inside `fn` and create variables with the > value `undefined`. This is "var hoisting". > 4. Look for all let/const declarations, and create uninitialized variables > (these will throw when accessed) > 5. A bunch of other stuff I'm skipping > 6. Execute the function body itself. > > A very similar process happens for modules. You can think of it like steps > 1-4 running, then before executing the module body (step 6), we recursively > do this same process on every imported module. So by the time any module > gets to step 6, every imported module, and every module those modules > depend on, will have executed step 1-4, and have scopes that have been > created, and variables that have been declared (possibly with a value, or > possibly left uninitialized). > > So by the time you get to step 6, there isn't a "beyond the module > boundary", when you access the imported variables in your module, the JS > engine will reach across the module boundary for you, to get the current > value of the variable in the imported module. This behavior of reaching > across module scopes is what module syntax allows, and it is what enables > live binding. > > Because of this live behavior, if you imported something that was defined > with `let`, like your `let C;` example, it would cause a TDZ error because > the variable was still "uninitialized", whereas if you make it a `var`, it > will be initialized to `undefined`. > > > If I use the `var` method as you proposed (which is working in my Babel > env), should I expect that method to always work in any theoretical > 100%-implemented-to-spec ES6 environment, not just in Babel? > > Correct. > > On Tue, Aug 23, 2016 at 8:17 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> > Damn, that's a Babel bug with the block scoping logic. That said, as in >> my example, that needs to be `var C;` anyway, `let` would throw (in an >> environment with working TDZ anyway). Changing it to `var` also stops the >> duplicate printing. >> >> Should I open an issue there? And I think you're right about it should >> throw, because it is strange that the function can be executed before the >> module is ever evaluated (that seems like it should be impossible), and >> therefore a TDZ error would happen because the `let` line wasn't >> theoretically evaluated yet. >> >> I don't see how it is possible with `var`. How is it that `var`s or >> `function`s can be hoisted *out of* the module? Is that part of spec? If >> so, then that is different hoisting from function-based hoisting of pre-ES6. >> >> I was under the impression that the modules were like a function, and >> hoisting would only happen inside the module. In that case, the `initC` >> function could not possibly be available until the `C` module itself was >> evaluated, so I was expecting for there to be an `undefined` error when >> `initC` was called before the `C` module was evaluated. >> >> You say that >> >> > which means you can import and call a function declaration from any >> module, even if that module hasn't started the `Evaluation` phase yet, the >> same way it'd work with other cases of hoisting, where execution hasn't >> reached the function declaration, but it is available early. >> >> Which makes sense based on what I see happening, but it seems strange >> because it means that the scope of the module (as far as hoisting is >> concerned) is not the module itself, but rather some outer scope that wraps >> *all* the modules that import a given symbol. >> >> You said, >> >> > the same way it'd work with other cases of hoisting, where execution >> hasn't reached the function declaration, but it is available early. >> >> Pre-ES6 "hoisting" in javascript happens on boundaries set by >> `function`s, and I thought that modules would be similar to function >> bodies, and therefore I thought that hoisting would be limited to within a >> module and did not expect hoisting to go beyond the module boundary to some >> scope that encompasses multiple modules. That to me is a different type of >> "hoisting" than what I know from pre-ES6 JavaScript's function-scope >> hoisting so it isn't necessarily the "same way it'd work with other cases >> of hoisting"; there is definitely some ES6-module-specific stuff happening >> that is a little different from pre-ES6 function-scope hoisting. (sidenote: >> I've asked the awesome Axel of 2ality to add these useful details to his >> articles.) >> >> If I use the `var` method as you proposed (which is working in my Babel >> env), should I expect that method to always work in any theoretical >> 100%-implemented-to-spec ES6 environment, not just in Babel? >> >> If so, then this may be one of the rare cases of "when we'd want to >> actually use `var` instead of `let`" besides for cases when we want pre-ES6 >> hoisting which I think should be generally avoided in order to make code >> less error-prone and easier to understand. This behavior would have been >> nearly-impossible to know about without the knowledge gained from this >> conversation (or from reading the spec in depth which can be difficult). >> >> >> >> */#!/*JoePea >> >> On Tue, Aug 16, 2016 at 10:48 AM, Logan Smyth <loganfsmyth at gmail.com> >> wrote: >> >>> > Your `initC` solution is working in Meteor (Babel + Reify) and >>> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >>> two C variables instead of one. The following code is based on yours, and >>> the `console.log('initC!!!')` statement unexpectedly executes twice, and >>> you'll see output like this: >>> >>> Damn, that's a Babel bug with the block scoping logic. That said, as in >>> my example, that needs to be `var C;` anyway, `let` would throw (in an >>> environment with working TDZ anyway). Changing it to `var` also stops the >>> duplicate printing. >>> >>> > I'm also not sure how `initC` can be defined when it is called in the >>> B module, which is evaluated before C and A. The evaluation order is B, C, >>> A (depth first). Does the `initC` function get hoisted into a scope common >>> with all three modules? That is the only way that would seem to explain it, >>> but that seems to go against the intuition I had that each module had it's >>> own module scope (as if it were wrapped inside a `function() {}`, and >>> therefore I thought the `initC` function would be hoisted within the C >>> module, and that with B being evaluated first I thought an "undefined" >>> error would be thrown when it tried to execute `initC` (but that is not >>> happening!). How is it that `initC` can be available to the B module before >>> C is evaluated? >>> >>> There are two separate pieces to executing a module, `Instantiation`, >>> and `Evaluation`, which are what comes into play here. When you tell a JS >>> environment to execute a file, it will instantiate every ES6 module in >>> dependency graph before beginning to execute _any_ of the modules. Babel >>> does its best to simulate this behavior, though it's not perfect at it. One >>> of the things that happens during module instantiation is that hoisted >>> declarations are initialized, which means you can import and call a >>> function declaration from any module, even if that module hasn't started >>> the `Evaluation` phase yet, the same way it'd work with other cases of >>> hoisting, where execution hasn't reached the function declaration, but it >>> is available early. >>> >>> This behavior is why you can't use `let C` there, because when `B` is >>> being evaluated, the `let C` line won't have run because the Evaluation >>> phase of `C` hasn't started yet. You can however access the `initC` >>> function because it is a function declaration. As long as there are no TDZ >>> errors in what you're doing, that function can do whatever it would like, >>> assuming it doesn't depend on other stuff that would require `Evaluation` >>> to have finished in `C`, the same as what happens with hoisting normally. >>> That means for instance you couldn't do >>> >>> import B from './B'; >>> var SOME_CONSTANT = "hello"; >>> >>> export function initC(){ >>> return SOME_CONSTANT; >>> } >>> >>> because calling `initC` here would return `undefined` if called from >>> inside a dependency cycle from `B`. >>> >>> On Sat, Aug 13, 2016 at 9:09 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> Hi Logan, >>>> >>>> > The example I posted works properly with Babel's live-binding >>>> implementation and should require less repetition. What were your thoughts >>>> on it? >>>> >>>> Your `initC` solution is working in Meteor (Babel + Reify) and >>>> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >>>> two C variables instead of one. The following code is based on yours, and >>>> the `console.log('initC!!!')` statement unexpectedly executes twice, and >>>> you'll see output like this: >>>> >>>> ``` >>>> module B >>>> initC!!! >>>> module C >>>> initC!!! >>>> module A >>>> function A() { ... } >>>> function B() { ... } >>>> Entrypoint A {} >>>> ``` >>>> >>>> I'm also not sure how `initC` can be defined when it is called in the B >>>> module, which is evaluated before C and A. The evaluation order is B, C, A >>>> (depth first). Does the `initC` function get hoisted into a scope common >>>> with all three modules? That is the only way that would seem to explain it, >>>> but that seems to go against the intuition I had that each module had it's >>>> own module scope (as if it were wrapped inside a `function() {}`, and >>>> therefore I thought the `initC` function would be hoisted within the C >>>> module, and that with B being evaluated first I thought an "undefined" >>>> error would be thrown when it tried to execute `initC` (but that is not >>>> happening!). How is it that `initC` can be available to the B module before >>>> C is evaluated? >>>> >>>> This is the code I have: >>>> >>>> ```js >>>> // --- Entrypoint >>>> import A from './A' >>>> console.log('Entrypoint', new A) >>>> ``` >>>> >>>> ```js >>>> // --- Module A >>>> >>>> import C, {initC} from './C' >>>> >>>> console.log('module A') >>>> initC() >>>> >>>> class A extends C { >>>> // ... >>>> } >>>> >>>> export {A as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module B >>>> >>>> import C, {initC} from './C' >>>> >>>> console.log('module B') >>>> initC() >>>> >>>> class B extends C { >>>> // ... >>>> } >>>> >>>> export {B as default} >>>> ``` >>>> >>>> ```js >>>> // --- Module C >>>> >>>> import A from './A' >>>> import B from './B' >>>> >>>> console.log('module C') >>>> let C >>>> >>>> export function initC(){ >>>> if (C) return >>>> >>>> console.log('initC!!!') >>>> >>>> C = class C { >>>> constructor() { >>>> // this may run later, after all three modules are >>>> evaluated, or >>>> // possibly never. >>>> console.log(A) >>>> console.log(B) >>>> } >>>> } >>>> } >>>> >>>> initC() >>>> >>>> export {C as default} >>>> ``` >>>> >>>> */#!/*JoePea >>>> >>>> On Thu, Aug 11, 2016 at 10:26 AM, Logan Smyth <loganfsmyth at gmail.com> >>>> wrote: >>>> >>>>> Keep in mind `let A = A;` is a TDZ error in any real ES6 environment. >>>>> >>>>> The example I posted works properly with Babel's live-binding >>>>> implementation and should require less repetition. What were your thoughts >>>>> on it? >>>>> >>>>> On Thu, Aug 11, 2016 at 12:23 AM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> Alright, so I believe I have found the solution. It is not possible >>>>>> to guarantee a certain module evaluation order, but using some clever (but >>>>>> tedious) conditional checking I believe the problem is solved (with two >>>>>> caveats listed after): >>>>>> >>>>>> ```js >>>>>> // --- Entrypoint >>>>>> import A from './A' >>>>>> console.log('Entrypoint', new A) >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module A >>>>>> >>>>>> import C from './C' >>>>>> import {setUpB} from './B' >>>>>> >>>>>> let A >>>>>> >>>>>> export >>>>>> function setUpA(C) { >>>>>> >>>>>> if (!A) { >>>>>> A = class A extends C { >>>>>> // ... >>>>>> } >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> if (setUpA && C) setUpA(C) >>>>>> if (setUpB && C) setUpB(C) >>>>>> >>>>>> export {A as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module B >>>>>> >>>>>> import C from './C' >>>>>> import {setUpA} from './A' >>>>>> >>>>>> let B >>>>>> >>>>>> export >>>>>> function setUpB(C) { >>>>>> >>>>>> if (!B) { >>>>>> B = class B extends C { >>>>>> // ... >>>>>> } >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> if (setUpA && C) setUpA(C) >>>>>> if (setUpB && C) setUpB(C) >>>>>> >>>>>> export {B as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module C >>>>>> >>>>>> import A, {setUpA} from './A' >>>>>> import B, {setUpB} from './B' >>>>>> >>>>>> class C { >>>>>> constructor() { >>>>>> // this may run later, after all three modules are evaluated, >>>>>> or >>>>>> // possibly never. >>>>>> console.log(A) >>>>>> console.log(B) >>>>>> } >>>>>> } >>>>>> >>>>>> if (setUpA && C) setUpA(C) >>>>>> if (setUpB && C) setUpB(C) >>>>>> >>>>>> export {C as default} >>>>>> ``` >>>>>> >>>>>> The caveat is that this fails in both Babel environments and in >>>>>> Rollup. For it to work in Babel environments, `let A` and `let B` have to >>>>>> be changed to `let A = A` and `let B = B`, as per the [fault in Babel's >>>>>> ES2015-to-CommonJS implementation](https://github >>>>>> .com/meteor/meteor/issues/7621#issuecomment-238992688) pointed out >>>>>> by Ben Newman. And it fails in Rollup because Rollup [isn't really creating >>>>>> live bindings](https://github.com/rollup/rollup/issues/845), which >>>>>> is fine in most cases, but doesn't work with these circular dependencies. >>>>>> The Rollup output does not create the C reference before it is ever used in >>>>>> the first pair of conditional checks, unlike what (I think) would happen >>>>>> with real live bindings (please correct me if wrong). To understand what I >>>>>> mean in the case of Rollup, just run `if (FOO) console.log(FOO)` in your >>>>>> console, and you'll get an error because FOO is not defined. Had the >>>>>> bindings been live, then FOO *would* be defined. >>>>>> >>>>>> >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>>> On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>> >>>>>>> I found a solution that works in environments compiled by Babel, >>>>>>> using the [workaround suggested by Ben Newman]( >>>>>>> https://github.com/meteor/meteor/issues/7621#issuecomment-238992688 >>>>>>> ): >>>>>>> >>>>>>> ```js >>>>>>> // --- Module A >>>>>>> >>>>>>> import C from './C' >>>>>>> >>>>>>> let A = A // @benjamn's workaround applied >>>>>>> >>>>>>> export >>>>>>> function setUpA(C) { >>>>>>> >>>>>>> A = class A extends C { >>>>>>> // ... >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> export {A as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module B >>>>>>> >>>>>>> import C from './C' >>>>>>> >>>>>>> let B = B // @benjamn's workaround applied >>>>>>> >>>>>>> export >>>>>>> function setUpB(C) { >>>>>>> >>>>>>> B = class B extends C { >>>>>>> // ... >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> export {B as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module C >>>>>>> >>>>>>> import A, {setUpA} from './A' >>>>>>> import B, {setUpB} from './B' >>>>>>> >>>>>>> let C = class C { >>>>>>> constructor() { >>>>>>> // this may run later, after all three modules are >>>>>>> evaluated, or >>>>>>> // possibly never. >>>>>>> console.log(A) >>>>>>> console.log(B) >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> setUpA(C) >>>>>>> setUpB(C) >>>>>>> >>>>>>> export {C as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Entrypoint >>>>>>> >>>>>>> import A from './A' >>>>>>> console.log('Entrypoint', new A) // runs the console.logs in the C >>>>>>> constructor. >>>>>>> ``` >>>>>>> >>>>>>> >>>>>>> Although that works in my environment which is compiled from ES6 >>>>>>> modules to CommonJS by Babel, it [doesn't work in Rollup.js]( >>>>>>> http://goo.gl/PXXBKI), and may not work in other ES6 module >>>>>>> implementations. >>>>>>> >>>>>>> Is there some solution that will theoretically work in any ES6 >>>>>>> module environment? >>>>>>> >>>>>>> */#!/*JoePea >>>>>>> >>>>>> >>>>>> >>>>>> _______________________________________________ >>>>>> es-discuss mailing list >>>>>> es-discuss at mozilla.org >>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>> >>>>>> >>>>> >>>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20161008/2fd2c503/attachment-0001.html>
Yep, I believe that the first error was solved by moving the import statement to the top!
As for the second error, I think (but not yet sure) that the problem might be due to the circular dependency `src/motor-html/node ->
src/motor/Transformable -> src/motor/Sizeable -> src/motor/Scene ->
src/motor/ImperativeBase -> src/motor/Node -> src/motor-html/node`.
The problem will be solved after I re-organize some things, but if you have any suggestions it would be greatly appreciated!
I'm still not sure why it works in Reify and not in Webpack.
Yep, I believe that the first error was solved by moving the import statement to the top! As for the second error, I think (but not yet sure) that the problem might be due to the circular dependency `src/motor-html/node -> src/motor/Transformable -> src/motor/Sizeable -> src/motor/Scene -> src/motor/ImperativeBase -> src/motor/Node -> src/motor-html/node`. The problem will be solved after I re-organize some things, but if you have any suggestions it would be greatly appreciated! I'm still not sure why it works in Reify and not in Webpack. - Joe */#!/*JoePea On Sat, Oct 8, 2016 at 5:23 PM, /#!/JoePea <joe at trusktr.io> wrote: > Hello Logan, I wonder if I can borrow your expertise for just a moment: > > I ran into another problem. Suppose I have the following code which is > very similar to the code I had before (using the `var` trick along with > `initC` function), but now Module C imports `someFunction` from a newly > added module. > > The problem is that module B will be evaluated before both Module C and > the utilities module. When `initC` is called inside of the B module, the > `initC` function will try and call `someFunction` but there will be an > error because the utilities module was not yet evaluated, so `someFunction` > is undefined. > > What would you recommend as a solution? > > Here's the code: > > ```js > // --- Entrypoint > import A from './A' > console.log('Entrypoint', new A) > ``` > > ```js > // --- Module A > > import C, {initC} from './C' > > console.log('module A') > initC() > > class A extends C { > // ... > } > > export {A as default} > ``` > > ```js > // --- Module B > > import C, {initC} from './C' > > console.log('module B') > initC() > > class B extends C { > // ... > } > > export {B as default} > ``` > > ```js > // --- Module C > > import A from './A' > import B from './B' > import {someFunction} from './utilities' > > console.log('module C') > > var C > > initC() > > export function initC(){ > if (C) return > > console.log('initC!!!') > someFunction() > > C = class C { > constructor() { > // this may run later, after all three modules are evaluated, > or > // possibly never. > console.log(A) > console.log(B) > } > } > } > > export {C as default} > ``` > > ```js > // utilities.js > > var someFunction = () => {...} > > export { > someFunction, > } > ``` > > I think a possible (though strange) solution is to change the import order > in Module C, f.e.: > > ```js > import A from './A' > import B from './B' > import {someFunction} from './utilities' > ``` > > I haven't actually tested that example code, but I believe I'm having a > problem like this with Webpack+Babel here: https://github.com/trusktr/ > infamous/tree/circular-dep-bug > > In this case, there is a circular dependency between `src/motor/Scene` and > `src/motor/Sizeable` (which I've already planned to eliminate, but am still > curious about how I would solve it without eliminating the circular dep). > The modules import the defaults of each other, and I'm using the `var > Sizeable/initSizeable` trick. However, there's a third module, `Utility.js` > from which the `Sizeable` module needs to import something to use inside of > the `initSizeable` function. > > You can experience the error by simply opening `motor-scratch.html` in > your browser, in which case you'll get an error like > > ``` > Sizeable.js:328: Uncaught TypeError: Cannot read property ' > makeLowercaseSetterAliases' of undefined > ``` > > where `makeLowercaseSetterAliases` is akin to the `someFunction` of my > earlier example. The odd thing is that everything works fine in Meteor's > Reify environment, and I'm only getting this error when using the bundle > made by Webpack+Babel. > > If I re-order the dependencies in `src/motor/Sizeable` so that they look > like this: > > ```js > import { makeLowercaseSetterAliases } from './Utility' > import XYZValues from './XYZValues' > import Motor from './Motor' > import Scene from './Scene' > ``` > > then the error goes away, but I get another error: > > ``` > node.js:129: Uncaught TypeError: Cannot read property 'prototype' of > undefined > ``` > > I have a feeling that moving the import to the beginning solved the > problem, and that now I have another problem... > > If you'd like to test changes (f.e. moving the import statement), you can > simply: > > ``` > npm install > npm run watch > ``` > > which will automatically compile the global.js file after making changes > to anything in `src`, and you can reload the page in your browser. > > Any and all input you may have on this would be greatly appreciated! > > Thanks a ton! > > All the best, > - Joe > > > */#!/*JoePea > > On Tue, Aug 23, 2016 at 8:55 PM, Logan Smyth <loganfsmyth at gmail.com> > wrote: > >> > Should I open an issue there? And I think you're right about it should >> throw, because it is strange that the function can be executed before the >> module is ever evaluated (that seems like it should be impossible), and >> therefore a TDZ error would happen because the `let` line wasn't >> theoretically evaluated yet. >> >> If you'd like, go for it. To note, Babel 6 doesn't implement TDZ at all >> at the moment anyway. >> >> > I don't see how it is possible with `var`. How is it that `var`s or >> `function`s can be hoisted *out of* the module? Is that part of spec? If >> so, then that is different hoisting from function-based hoisting of pre-ES6. >> >> They are not hoisted out of the module. It seems like you may be >> misunderstanding how modules are linked together. >> >> > Pre-ES6 "hoisting" in javascript happens on boundaries set by >> `function`s, and I thought that modules would be similar to function >> bodies, and therefore I thought that hoisting would be limited to within a >> module and did not expect hoisting to go beyond the module boundary to some >> scope that encompasses multiple modules. That to me is a different type of >> "hoisting" than what I know from pre-ES6 JavaScript's function-scope >> hoisting so it isn't necessarily the "same way it'd work with other cases >> of hoisting"; there is definitely some ES6-module-specific stuff happening >> that is a little different from pre-ES6 function-scope hoisting. (sidenote: >> I've asked the awesome Axel of 2ality to add these useful details to his >> articles.) >> >> Hoisting of var and functions behaves the same way in ES6 and in ES5 and >> you are correct there is no "beyond the module boundary". Let's clarify >> hoisting in a function. >> >> ``` >> function fn() { >> console.log(inner()); >> >> function inner(){ return "hello world"; } >> } >> fn(); >> ``` >> in this context, when an engine executes `fn()`, before any execution has >> happened inside the `fn`, the engine does the following >> >> 1. Create the conceptual function "scope", which exists immediately as >> soon as the function is called. >> 2. Look for all function declarations inside `fn` and create their >> variables and assign their values to point to function objects. This is >> "function hoisting". >> 3. Look for all var declarations inside `fn` and create variables with >> the value `undefined`. This is "var hoisting". >> 4. Look for all let/const declarations, and create uninitialized >> variables (these will throw when accessed) >> 5. A bunch of other stuff I'm skipping >> 6. Execute the function body itself. >> >> A very similar process happens for modules. You can think of it like >> steps 1-4 running, then before executing the module body (step 6), we >> recursively do this same process on every imported module. So by the time >> any module gets to step 6, every imported module, and every module those >> modules depend on, will have executed step 1-4, and have scopes that have >> been created, and variables that have been declared (possibly with a value, >> or possibly left uninitialized). >> >> So by the time you get to step 6, there isn't a "beyond the module >> boundary", when you access the imported variables in your module, the JS >> engine will reach across the module boundary for you, to get the current >> value of the variable in the imported module. This behavior of reaching >> across module scopes is what module syntax allows, and it is what enables >> live binding. >> >> Because of this live behavior, if you imported something that was defined >> with `let`, like your `let C;` example, it would cause a TDZ error because >> the variable was still "uninitialized", whereas if you make it a `var`, it >> will be initialized to `undefined`. >> >> > If I use the `var` method as you proposed (which is working in my >> Babel env), should I expect that method to always work in any theoretical >> 100%-implemented-to-spec ES6 environment, not just in Babel? >> >> Correct. >> >> On Tue, Aug 23, 2016 at 8:17 PM, /#!/JoePea <joe at trusktr.io> wrote: >> >>> > Damn, that's a Babel bug with the block scoping logic. That said, as >>> in my example, that needs to be `var C;` anyway, `let` would throw (in an >>> environment with working TDZ anyway). Changing it to `var` also stops the >>> duplicate printing. >>> >>> Should I open an issue there? And I think you're right about it should >>> throw, because it is strange that the function can be executed before the >>> module is ever evaluated (that seems like it should be impossible), and >>> therefore a TDZ error would happen because the `let` line wasn't >>> theoretically evaluated yet. >>> >>> I don't see how it is possible with `var`. How is it that `var`s or >>> `function`s can be hoisted *out of* the module? Is that part of spec? If >>> so, then that is different hoisting from function-based hoisting of pre-ES6. >>> >>> I was under the impression that the modules were like a function, and >>> hoisting would only happen inside the module. In that case, the `initC` >>> function could not possibly be available until the `C` module itself was >>> evaluated, so I was expecting for there to be an `undefined` error when >>> `initC` was called before the `C` module was evaluated. >>> >>> You say that >>> >>> > which means you can import and call a function declaration from any >>> module, even if that module hasn't started the `Evaluation` phase yet, the >>> same way it'd work with other cases of hoisting, where execution hasn't >>> reached the function declaration, but it is available early. >>> >>> Which makes sense based on what I see happening, but it seems strange >>> because it means that the scope of the module (as far as hoisting is >>> concerned) is not the module itself, but rather some outer scope that wraps >>> *all* the modules that import a given symbol. >>> >>> You said, >>> >>> > the same way it'd work with other cases of hoisting, where execution >>> hasn't reached the function declaration, but it is available early. >>> >>> Pre-ES6 "hoisting" in javascript happens on boundaries set by >>> `function`s, and I thought that modules would be similar to function >>> bodies, and therefore I thought that hoisting would be limited to within a >>> module and did not expect hoisting to go beyond the module boundary to some >>> scope that encompasses multiple modules. That to me is a different type of >>> "hoisting" than what I know from pre-ES6 JavaScript's function-scope >>> hoisting so it isn't necessarily the "same way it'd work with other cases >>> of hoisting"; there is definitely some ES6-module-specific stuff happening >>> that is a little different from pre-ES6 function-scope hoisting. (sidenote: >>> I've asked the awesome Axel of 2ality to add these useful details to his >>> articles.) >>> >>> If I use the `var` method as you proposed (which is working in my Babel >>> env), should I expect that method to always work in any theoretical >>> 100%-implemented-to-spec ES6 environment, not just in Babel? >>> >>> If so, then this may be one of the rare cases of "when we'd want to >>> actually use `var` instead of `let`" besides for cases when we want pre-ES6 >>> hoisting which I think should be generally avoided in order to make code >>> less error-prone and easier to understand. This behavior would have been >>> nearly-impossible to know about without the knowledge gained from this >>> conversation (or from reading the spec in depth which can be difficult). >>> >>> >>> >>> */#!/*JoePea >>> >>> On Tue, Aug 16, 2016 at 10:48 AM, Logan Smyth <loganfsmyth at gmail.com> >>> wrote: >>> >>>> > Your `initC` solution is working in Meteor (Babel + Reify) and >>>> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >>>> two C variables instead of one. The following code is based on yours, and >>>> the `console.log('initC!!!')` statement unexpectedly executes twice, and >>>> you'll see output like this: >>>> >>>> Damn, that's a Babel bug with the block scoping logic. That said, as in >>>> my example, that needs to be `var C;` anyway, `let` would throw (in an >>>> environment with working TDZ anyway). Changing it to `var` also stops the >>>> duplicate printing. >>>> >>>> > I'm also not sure how `initC` can be defined when it is called in >>>> the B module, which is evaluated before C and A. The evaluation order is B, >>>> C, A (depth first). Does the `initC` function get hoisted into a scope >>>> common with all three modules? That is the only way that would seem to >>>> explain it, but that seems to go against the intuition I had that each >>>> module had it's own module scope (as if it were wrapped inside a >>>> `function() {}`, and therefore I thought the `initC` function would be >>>> hoisted within the C module, and that with B being evaluated first I >>>> thought an "undefined" error would be thrown when it tried to execute >>>> `initC` (but that is not happening!). How is it that `initC` can be >>>> available to the B module before C is evaluated? >>>> >>>> There are two separate pieces to executing a module, `Instantiation`, >>>> and `Evaluation`, which are what comes into play here. When you tell a JS >>>> environment to execute a file, it will instantiate every ES6 module in >>>> dependency graph before beginning to execute _any_ of the modules. Babel >>>> does its best to simulate this behavior, though it's not perfect at it. One >>>> of the things that happens during module instantiation is that hoisted >>>> declarations are initialized, which means you can import and call a >>>> function declaration from any module, even if that module hasn't started >>>> the `Evaluation` phase yet, the same way it'd work with other cases of >>>> hoisting, where execution hasn't reached the function declaration, but it >>>> is available early. >>>> >>>> This behavior is why you can't use `let C` there, because when `B` is >>>> being evaluated, the `let C` line won't have run because the Evaluation >>>> phase of `C` hasn't started yet. You can however access the `initC` >>>> function because it is a function declaration. As long as there are no TDZ >>>> errors in what you're doing, that function can do whatever it would like, >>>> assuming it doesn't depend on other stuff that would require `Evaluation` >>>> to have finished in `C`, the same as what happens with hoisting normally. >>>> That means for instance you couldn't do >>>> >>>> import B from './B'; >>>> var SOME_CONSTANT = "hello"; >>>> >>>> export function initC(){ >>>> return SOME_CONSTANT; >>>> } >>>> >>>> because calling `initC` here would return `undefined` if called from >>>> inside a dependency cycle from `B`. >>>> >>>> On Sat, Aug 13, 2016 at 9:09 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>> >>>>> Hi Logan, >>>>> >>>>> > The example I posted works properly with Babel's live-binding >>>>> implementation and should require less repetition. What were your thoughts >>>>> on it? >>>>> >>>>> Your `initC` solution is working in Meteor (Babel + Reify) and >>>>> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >>>>> two C variables instead of one. The following code is based on yours, and >>>>> the `console.log('initC!!!')` statement unexpectedly executes twice, and >>>>> you'll see output like this: >>>>> >>>>> ``` >>>>> module B >>>>> initC!!! >>>>> module C >>>>> initC!!! >>>>> module A >>>>> function A() { ... } >>>>> function B() { ... } >>>>> Entrypoint A {} >>>>> ``` >>>>> >>>>> I'm also not sure how `initC` can be defined when it is called in the >>>>> B module, which is evaluated before C and A. The evaluation order is B, C, >>>>> A (depth first). Does the `initC` function get hoisted into a scope common >>>>> with all three modules? That is the only way that would seem to explain it, >>>>> but that seems to go against the intuition I had that each module had it's >>>>> own module scope (as if it were wrapped inside a `function() {}`, and >>>>> therefore I thought the `initC` function would be hoisted within the C >>>>> module, and that with B being evaluated first I thought an "undefined" >>>>> error would be thrown when it tried to execute `initC` (but that is not >>>>> happening!). How is it that `initC` can be available to the B module before >>>>> C is evaluated? >>>>> >>>>> This is the code I have: >>>>> >>>>> ```js >>>>> // --- Entrypoint >>>>> import A from './A' >>>>> console.log('Entrypoint', new A) >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module A >>>>> >>>>> import C, {initC} from './C' >>>>> >>>>> console.log('module A') >>>>> initC() >>>>> >>>>> class A extends C { >>>>> // ... >>>>> } >>>>> >>>>> export {A as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module B >>>>> >>>>> import C, {initC} from './C' >>>>> >>>>> console.log('module B') >>>>> initC() >>>>> >>>>> class B extends C { >>>>> // ... >>>>> } >>>>> >>>>> export {B as default} >>>>> ``` >>>>> >>>>> ```js >>>>> // --- Module C >>>>> >>>>> import A from './A' >>>>> import B from './B' >>>>> >>>>> console.log('module C') >>>>> let C >>>>> >>>>> export function initC(){ >>>>> if (C) return >>>>> >>>>> console.log('initC!!!') >>>>> >>>>> C = class C { >>>>> constructor() { >>>>> // this may run later, after all three modules are >>>>> evaluated, or >>>>> // possibly never. >>>>> console.log(A) >>>>> console.log(B) >>>>> } >>>>> } >>>>> } >>>>> >>>>> initC() >>>>> >>>>> export {C as default} >>>>> ``` >>>>> >>>>> */#!/*JoePea >>>>> >>>>> On Thu, Aug 11, 2016 at 10:26 AM, Logan Smyth <loganfsmyth at gmail.com> >>>>> wrote: >>>>> >>>>>> Keep in mind `let A = A;` is a TDZ error in any real ES6 >>>>>> environment. >>>>>> >>>>>> The example I posted works properly with Babel's live-binding >>>>>> implementation and should require less repetition. What were your thoughts >>>>>> on it? >>>>>> >>>>>> On Thu, Aug 11, 2016 at 12:23 AM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>> >>>>>>> Alright, so I believe I have found the solution. It is not possible >>>>>>> to guarantee a certain module evaluation order, but using some clever (but >>>>>>> tedious) conditional checking I believe the problem is solved (with two >>>>>>> caveats listed after): >>>>>>> >>>>>>> ```js >>>>>>> // --- Entrypoint >>>>>>> import A from './A' >>>>>>> console.log('Entrypoint', new A) >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module A >>>>>>> >>>>>>> import C from './C' >>>>>>> import {setUpB} from './B' >>>>>>> >>>>>>> let A >>>>>>> >>>>>>> export >>>>>>> function setUpA(C) { >>>>>>> >>>>>>> if (!A) { >>>>>>> A = class A extends C { >>>>>>> // ... >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> if (setUpA && C) setUpA(C) >>>>>>> if (setUpB && C) setUpB(C) >>>>>>> >>>>>>> export {A as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module B >>>>>>> >>>>>>> import C from './C' >>>>>>> import {setUpA} from './A' >>>>>>> >>>>>>> let B >>>>>>> >>>>>>> export >>>>>>> function setUpB(C) { >>>>>>> >>>>>>> if (!B) { >>>>>>> B = class B extends C { >>>>>>> // ... >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> if (setUpA && C) setUpA(C) >>>>>>> if (setUpB && C) setUpB(C) >>>>>>> >>>>>>> export {B as default} >>>>>>> ``` >>>>>>> >>>>>>> ```js >>>>>>> // --- Module C >>>>>>> >>>>>>> import A, {setUpA} from './A' >>>>>>> import B, {setUpB} from './B' >>>>>>> >>>>>>> class C { >>>>>>> constructor() { >>>>>>> // this may run later, after all three modules are >>>>>>> evaluated, or >>>>>>> // possibly never. >>>>>>> console.log(A) >>>>>>> console.log(B) >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> if (setUpA && C) setUpA(C) >>>>>>> if (setUpB && C) setUpB(C) >>>>>>> >>>>>>> export {C as default} >>>>>>> ``` >>>>>>> >>>>>>> The caveat is that this fails in both Babel environments and in >>>>>>> Rollup. For it to work in Babel environments, `let A` and `let B` have to >>>>>>> be changed to `let A = A` and `let B = B`, as per the [fault in Babel's >>>>>>> ES2015-to-CommonJS implementation](https://github >>>>>>> .com/meteor/meteor/issues/7621#issuecomment-238992688) pointed out >>>>>>> by Ben Newman. And it fails in Rollup because Rollup [isn't really creating >>>>>>> live bindings](https://github.com/rollup/rollup/issues/845), which >>>>>>> is fine in most cases, but doesn't work with these circular dependencies. >>>>>>> The Rollup output does not create the C reference before it is ever used in >>>>>>> the first pair of conditional checks, unlike what (I think) would happen >>>>>>> with real live bindings (please correct me if wrong). To understand what I >>>>>>> mean in the case of Rollup, just run `if (FOO) console.log(FOO)` in your >>>>>>> console, and you'll get an error because FOO is not defined. Had the >>>>>>> bindings been live, then FOO *would* be defined. >>>>>>> >>>>>>> >>>>>>> >>>>>>> */#!/*JoePea >>>>>>> >>>>>>> On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>> >>>>>>>> I found a solution that works in environments compiled by Babel, >>>>>>>> using the [workaround suggested by Ben Newman]( >>>>>>>> https://github.com/meteor/meteor/issues/7621#issuecomment-238992688 >>>>>>>> ): >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module A >>>>>>>> >>>>>>>> import C from './C' >>>>>>>> >>>>>>>> let A = A // @benjamn's workaround applied >>>>>>>> >>>>>>>> export >>>>>>>> function setUpA(C) { >>>>>>>> >>>>>>>> A = class A extends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> >>>>>>>> } >>>>>>>> >>>>>>>> export {A as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module B >>>>>>>> >>>>>>>> import C from './C' >>>>>>>> >>>>>>>> let B = B // @benjamn's workaround applied >>>>>>>> >>>>>>>> export >>>>>>>> function setUpB(C) { >>>>>>>> >>>>>>>> B = class B extends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> >>>>>>>> } >>>>>>>> >>>>>>>> export {B as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module C >>>>>>>> >>>>>>>> import A, {setUpA} from './A' >>>>>>>> import B, {setUpB} from './B' >>>>>>>> >>>>>>>> let C = class C { >>>>>>>> constructor() { >>>>>>>> // this may run later, after all three modules are >>>>>>>> evaluated, or >>>>>>>> // possibly never. >>>>>>>> console.log(A) >>>>>>>> console.log(B) >>>>>>>> } >>>>>>>> } >>>>>>>> >>>>>>>> setUpA(C) >>>>>>>> setUpB(C) >>>>>>>> >>>>>>>> export {C as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Entrypoint >>>>>>>> >>>>>>>> import A from './A' >>>>>>>> console.log('Entrypoint', new A) // runs the console.logs in the C >>>>>>>> constructor. >>>>>>>> ``` >>>>>>>> >>>>>>>> >>>>>>>> Although that works in my environment which is compiled from ES6 >>>>>>>> modules to CommonJS by Babel, it [doesn't work in Rollup.js]( >>>>>>>> http://goo.gl/PXXBKI), and may not work in other ES6 module >>>>>>>> implementations. >>>>>>>> >>>>>>>> Is there some solution that will theoretically work in any ES6 >>>>>>>> module environment? >>>>>>>> >>>>>>>> */#!/*JoePea >>>>>>>> >>>>>>> >>>>>>> >>>>>>> _______________________________________________ >>>>>>> es-discuss mailing list >>>>>>> es-discuss at mozilla.org >>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>> >>>>>>> >>>>>> >>>>> >>>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20161008/446f8d13/attachment-0001.html>
I eliminated the circular dep, and all is well now. I suppose I learned something that is really good to know if ever dealing with circular deps:
Always import circular deps after non-circular deps.
f.e.
this is bad:
import CircularDep from 'somewhere'
import NonCircularDep from 'another-place'
and this is good:
import NonCircularDep from 'another-place'
import CircularDep from 'somewhere'
This ensures that the non-circular deps are evaluated and ready before hoisted circular-dep init functions are called (i.e. when using the var/initDep trick).
I eliminated the circular dep, and all is well now. I suppose I learned something that is really good to know if ever dealing with circular deps: Always import circular deps after non-circular deps. f.e. this is bad: ```js import CircularDep from 'somewhere' import NonCircularDep from 'another-place' ``` and this is good: ```js import NonCircularDep from 'another-place' import CircularDep from 'somewhere' ``` This ensures that the non-circular deps are evaluated and ready before hoisted circular-dep init functions are called (i.e. when using the var/initDep trick). */#!/*JoePea On Sat, Oct 8, 2016 at 7:46 PM, /#!/JoePea <joe at trusktr.io> wrote: > Yep, I believe that the first error was solved by moving the import > statement to the top! > > As for the second error, I think (but not yet sure) that the problem might > be due to the circular dependency `src/motor-html/node -> > src/motor/Transformable -> src/motor/Sizeable -> src/motor/Scene -> > src/motor/ImperativeBase -> src/motor/Node -> src/motor-html/node`. > > The problem will be solved after I re-organize some things, but if you > have any suggestions it would be greatly appreciated! > > I'm still not sure why it works in Reify and not in Webpack. > > - Joe > > > */#!/*JoePea > > On Sat, Oct 8, 2016 at 5:23 PM, /#!/JoePea <joe at trusktr.io> wrote: > >> Hello Logan, I wonder if I can borrow your expertise for just a moment: >> >> I ran into another problem. Suppose I have the following code which is >> very similar to the code I had before (using the `var` trick along with >> `initC` function), but now Module C imports `someFunction` from a newly >> added module. >> >> The problem is that module B will be evaluated before both Module C and >> the utilities module. When `initC` is called inside of the B module, the >> `initC` function will try and call `someFunction` but there will be an >> error because the utilities module was not yet evaluated, so `someFunction` >> is undefined. >> >> What would you recommend as a solution? >> >> Here's the code: >> >> ```js >> // --- Entrypoint >> import A from './A' >> console.log('Entrypoint', new A) >> ``` >> >> ```js >> // --- Module A >> >> import C, {initC} from './C' >> >> console.log('module A') >> initC() >> >> class A extends C { >> // ... >> } >> >> export {A as default} >> ``` >> >> ```js >> // --- Module B >> >> import C, {initC} from './C' >> >> console.log('module B') >> initC() >> >> class B extends C { >> // ... >> } >> >> export {B as default} >> ``` >> >> ```js >> // --- Module C >> >> import A from './A' >> import B from './B' >> import {someFunction} from './utilities' >> >> console.log('module C') >> >> var C >> >> initC() >> >> export function initC(){ >> if (C) return >> >> console.log('initC!!!') >> someFunction() >> >> C = class C { >> constructor() { >> // this may run later, after all three modules are evaluated, >> or >> // possibly never. >> console.log(A) >> console.log(B) >> } >> } >> } >> >> export {C as default} >> ``` >> >> ```js >> // utilities.js >> >> var someFunction = () => {...} >> >> export { >> someFunction, >> } >> ``` >> >> I think a possible (though strange) solution is to change the import >> order in Module C, f.e.: >> >> ```js >> import A from './A' >> import B from './B' >> import {someFunction} from './utilities' >> ``` >> >> I haven't actually tested that example code, but I believe I'm having a >> problem like this with Webpack+Babel here: https://github.com/trusktr/inf >> amous/tree/circular-dep-bug >> >> In this case, there is a circular dependency between `src/motor/Scene` >> and `src/motor/Sizeable` (which I've already planned to eliminate, but am >> still curious about how I would solve it without eliminating the circular >> dep). The modules import the defaults of each other, and I'm using the `var >> Sizeable/initSizeable` trick. However, there's a third module, `Utility.js` >> from which the `Sizeable` module needs to import something to use inside of >> the `initSizeable` function. >> >> You can experience the error by simply opening `motor-scratch.html` in >> your browser, in which case you'll get an error like >> >> ``` >> Sizeable.js:328: Uncaught TypeError: Cannot read property ' >> makeLowercaseSetterAliases' of undefined >> ``` >> >> where `makeLowercaseSetterAliases` is akin to the `someFunction` of my >> earlier example. The odd thing is that everything works fine in Meteor's >> Reify environment, and I'm only getting this error when using the bundle >> made by Webpack+Babel. >> >> If I re-order the dependencies in `src/motor/Sizeable` so that they look >> like this: >> >> ```js >> import { makeLowercaseSetterAliases } from './Utility' >> import XYZValues from './XYZValues' >> import Motor from './Motor' >> import Scene from './Scene' >> ``` >> >> then the error goes away, but I get another error: >> >> ``` >> node.js:129: Uncaught TypeError: Cannot read property 'prototype' of >> undefined >> ``` >> >> I have a feeling that moving the import to the beginning solved the >> problem, and that now I have another problem... >> >> If you'd like to test changes (f.e. moving the import statement), you can >> simply: >> >> ``` >> npm install >> npm run watch >> ``` >> >> which will automatically compile the global.js file after making changes >> to anything in `src`, and you can reload the page in your browser. >> >> Any and all input you may have on this would be greatly appreciated! >> >> Thanks a ton! >> >> All the best, >> - Joe >> >> >> */#!/*JoePea >> >> On Tue, Aug 23, 2016 at 8:55 PM, Logan Smyth <loganfsmyth at gmail.com> >> wrote: >> >>> > Should I open an issue there? And I think you're right about it >>> should throw, because it is strange that the function can be executed >>> before the module is ever evaluated (that seems like it should be >>> impossible), and therefore a TDZ error would happen because the `let` line >>> wasn't theoretically evaluated yet. >>> >>> If you'd like, go for it. To note, Babel 6 doesn't implement TDZ at all >>> at the moment anyway. >>> >>> > I don't see how it is possible with `var`. How is it that `var`s or >>> `function`s can be hoisted *out of* the module? Is that part of spec? If >>> so, then that is different hoisting from function-based hoisting of pre-ES6. >>> >>> They are not hoisted out of the module. It seems like you may be >>> misunderstanding how modules are linked together. >>> >>> > Pre-ES6 "hoisting" in javascript happens on boundaries set by >>> `function`s, and I thought that modules would be similar to function >>> bodies, and therefore I thought that hoisting would be limited to within a >>> module and did not expect hoisting to go beyond the module boundary to some >>> scope that encompasses multiple modules. That to me is a different type of >>> "hoisting" than what I know from pre-ES6 JavaScript's function-scope >>> hoisting so it isn't necessarily the "same way it'd work with other cases >>> of hoisting"; there is definitely some ES6-module-specific stuff happening >>> that is a little different from pre-ES6 function-scope hoisting. (sidenote: >>> I've asked the awesome Axel of 2ality to add these useful details to his >>> articles.) >>> >>> Hoisting of var and functions behaves the same way in ES6 and in ES5 and >>> you are correct there is no "beyond the module boundary". Let's clarify >>> hoisting in a function. >>> >>> ``` >>> function fn() { >>> console.log(inner()); >>> >>> function inner(){ return "hello world"; } >>> } >>> fn(); >>> ``` >>> in this context, when an engine executes `fn()`, before any execution >>> has happened inside the `fn`, the engine does the following >>> >>> 1. Create the conceptual function "scope", which exists immediately as >>> soon as the function is called. >>> 2. Look for all function declarations inside `fn` and create their >>> variables and assign their values to point to function objects. This is >>> "function hoisting". >>> 3. Look for all var declarations inside `fn` and create variables with >>> the value `undefined`. This is "var hoisting". >>> 4. Look for all let/const declarations, and create uninitialized >>> variables (these will throw when accessed) >>> 5. A bunch of other stuff I'm skipping >>> 6. Execute the function body itself. >>> >>> A very similar process happens for modules. You can think of it like >>> steps 1-4 running, then before executing the module body (step 6), we >>> recursively do this same process on every imported module. So by the time >>> any module gets to step 6, every imported module, and every module those >>> modules depend on, will have executed step 1-4, and have scopes that have >>> been created, and variables that have been declared (possibly with a value, >>> or possibly left uninitialized). >>> >>> So by the time you get to step 6, there isn't a "beyond the module >>> boundary", when you access the imported variables in your module, the JS >>> engine will reach across the module boundary for you, to get the current >>> value of the variable in the imported module. This behavior of reaching >>> across module scopes is what module syntax allows, and it is what enables >>> live binding. >>> >>> Because of this live behavior, if you imported something that was >>> defined with `let`, like your `let C;` example, it would cause a TDZ error >>> because the variable was still "uninitialized", whereas if you make it a >>> `var`, it will be initialized to `undefined`. >>> >>> > If I use the `var` method as you proposed (which is working in my >>> Babel env), should I expect that method to always work in any theoretical >>> 100%-implemented-to-spec ES6 environment, not just in Babel? >>> >>> Correct. >>> >>> On Tue, Aug 23, 2016 at 8:17 PM, /#!/JoePea <joe at trusktr.io> wrote: >>> >>>> > Damn, that's a Babel bug with the block scoping logic. That said, as >>>> in my example, that needs to be `var C;` anyway, `let` would throw (in an >>>> environment with working TDZ anyway). Changing it to `var` also stops the >>>> duplicate printing. >>>> >>>> Should I open an issue there? And I think you're right about it should >>>> throw, because it is strange that the function can be executed before the >>>> module is ever evaluated (that seems like it should be impossible), and >>>> therefore a TDZ error would happen because the `let` line wasn't >>>> theoretically evaluated yet. >>>> >>>> I don't see how it is possible with `var`. How is it that `var`s or >>>> `function`s can be hoisted *out of* the module? Is that part of spec? If >>>> so, then that is different hoisting from function-based hoisting of pre-ES6. >>>> >>>> I was under the impression that the modules were like a function, and >>>> hoisting would only happen inside the module. In that case, the `initC` >>>> function could not possibly be available until the `C` module itself was >>>> evaluated, so I was expecting for there to be an `undefined` error when >>>> `initC` was called before the `C` module was evaluated. >>>> >>>> You say that >>>> >>>> > which means you can import and call a function declaration from any >>>> module, even if that module hasn't started the `Evaluation` phase yet, the >>>> same way it'd work with other cases of hoisting, where execution hasn't >>>> reached the function declaration, but it is available early. >>>> >>>> Which makes sense based on what I see happening, but it seems strange >>>> because it means that the scope of the module (as far as hoisting is >>>> concerned) is not the module itself, but rather some outer scope that wraps >>>> *all* the modules that import a given symbol. >>>> >>>> You said, >>>> >>>> > the same way it'd work with other cases of hoisting, where execution >>>> hasn't reached the function declaration, but it is available early. >>>> >>>> Pre-ES6 "hoisting" in javascript happens on boundaries set by >>>> `function`s, and I thought that modules would be similar to function >>>> bodies, and therefore I thought that hoisting would be limited to within a >>>> module and did not expect hoisting to go beyond the module boundary to some >>>> scope that encompasses multiple modules. That to me is a different type of >>>> "hoisting" than what I know from pre-ES6 JavaScript's function-scope >>>> hoisting so it isn't necessarily the "same way it'd work with other cases >>>> of hoisting"; there is definitely some ES6-module-specific stuff happening >>>> that is a little different from pre-ES6 function-scope hoisting. (sidenote: >>>> I've asked the awesome Axel of 2ality to add these useful details to his >>>> articles.) >>>> >>>> If I use the `var` method as you proposed (which is working in my Babel >>>> env), should I expect that method to always work in any theoretical >>>> 100%-implemented-to-spec ES6 environment, not just in Babel? >>>> >>>> If so, then this may be one of the rare cases of "when we'd want to >>>> actually use `var` instead of `let`" besides for cases when we want pre-ES6 >>>> hoisting which I think should be generally avoided in order to make code >>>> less error-prone and easier to understand. This behavior would have been >>>> nearly-impossible to know about without the knowledge gained from this >>>> conversation (or from reading the spec in depth which can be difficult). >>>> >>>> >>>> >>>> */#!/*JoePea >>>> >>>> On Tue, Aug 16, 2016 at 10:48 AM, Logan Smyth <loganfsmyth at gmail.com> >>>> wrote: >>>> >>>>> > Your `initC` solution is working in Meteor (Babel + Reify) and >>>>> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >>>>> two C variables instead of one. The following code is based on yours, and >>>>> the `console.log('initC!!!')` statement unexpectedly executes twice, and >>>>> you'll see output like this: >>>>> >>>>> Damn, that's a Babel bug with the block scoping logic. That said, as >>>>> in my example, that needs to be `var C;` anyway, `let` would throw (in an >>>>> environment with working TDZ anyway). Changing it to `var` also stops the >>>>> duplicate printing. >>>>> >>>>> > I'm also not sure how `initC` can be defined when it is called in >>>>> the B module, which is evaluated before C and A. The evaluation order is B, >>>>> C, A (depth first). Does the `initC` function get hoisted into a scope >>>>> common with all three modules? That is the only way that would seem to >>>>> explain it, but that seems to go against the intuition I had that each >>>>> module had it's own module scope (as if it were wrapped inside a >>>>> `function() {}`, and therefore I thought the `initC` function would be >>>>> hoisted within the C module, and that with B being evaluated first I >>>>> thought an "undefined" error would be thrown when it tried to execute >>>>> `initC` (but that is not happening!). How is it that `initC` can be >>>>> available to the B module before C is evaluated? >>>>> >>>>> There are two separate pieces to executing a module, `Instantiation`, >>>>> and `Evaluation`, which are what comes into play here. When you tell a JS >>>>> environment to execute a file, it will instantiate every ES6 module in >>>>> dependency graph before beginning to execute _any_ of the modules. Babel >>>>> does its best to simulate this behavior, though it's not perfect at it. One >>>>> of the things that happens during module instantiation is that hoisted >>>>> declarations are initialized, which means you can import and call a >>>>> function declaration from any module, even if that module hasn't started >>>>> the `Evaluation` phase yet, the same way it'd work with other cases of >>>>> hoisting, where execution hasn't reached the function declaration, but it >>>>> is available early. >>>>> >>>>> This behavior is why you can't use `let C` there, because when `B` is >>>>> being evaluated, the `let C` line won't have run because the Evaluation >>>>> phase of `C` hasn't started yet. You can however access the `initC` >>>>> function because it is a function declaration. As long as there are no TDZ >>>>> errors in what you're doing, that function can do whatever it would like, >>>>> assuming it doesn't depend on other stuff that would require `Evaluation` >>>>> to have finished in `C`, the same as what happens with hoisting normally. >>>>> That means for instance you couldn't do >>>>> >>>>> import B from './B'; >>>>> var SOME_CONSTANT = "hello"; >>>>> >>>>> export function initC(){ >>>>> return SOME_CONSTANT; >>>>> } >>>>> >>>>> because calling `initC` here would return `undefined` if called from >>>>> inside a dependency cycle from `B`. >>>>> >>>>> On Sat, Aug 13, 2016 at 9:09 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>> >>>>>> Hi Logan, >>>>>> >>>>>> > The example I posted works properly with Babel's live-binding >>>>>> implementation and should require less repetition. What were your thoughts >>>>>> on it? >>>>>> >>>>>> Your `initC` solution is working in Meteor (Babel + Reify) and >>>>>> Webpack+Babel, but the `initC` logic seems to run twice, as if there are >>>>>> two C variables instead of one. The following code is based on yours, and >>>>>> the `console.log('initC!!!')` statement unexpectedly executes twice, and >>>>>> you'll see output like this: >>>>>> >>>>>> ``` >>>>>> module B >>>>>> initC!!! >>>>>> module C >>>>>> initC!!! >>>>>> module A >>>>>> function A() { ... } >>>>>> function B() { ... } >>>>>> Entrypoint A {} >>>>>> ``` >>>>>> >>>>>> I'm also not sure how `initC` can be defined when it is called in the >>>>>> B module, which is evaluated before C and A. The evaluation order is B, C, >>>>>> A (depth first). Does the `initC` function get hoisted into a scope common >>>>>> with all three modules? That is the only way that would seem to explain it, >>>>>> but that seems to go against the intuition I had that each module had it's >>>>>> own module scope (as if it were wrapped inside a `function() {}`, and >>>>>> therefore I thought the `initC` function would be hoisted within the C >>>>>> module, and that with B being evaluated first I thought an "undefined" >>>>>> error would be thrown when it tried to execute `initC` (but that is not >>>>>> happening!). How is it that `initC` can be available to the B module before >>>>>> C is evaluated? >>>>>> >>>>>> This is the code I have: >>>>>> >>>>>> ```js >>>>>> // --- Entrypoint >>>>>> import A from './A' >>>>>> console.log('Entrypoint', new A) >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module A >>>>>> >>>>>> import C, {initC} from './C' >>>>>> >>>>>> console.log('module A') >>>>>> initC() >>>>>> >>>>>> class A extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> export {A as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module B >>>>>> >>>>>> import C, {initC} from './C' >>>>>> >>>>>> console.log('module B') >>>>>> initC() >>>>>> >>>>>> class B extends C { >>>>>> // ... >>>>>> } >>>>>> >>>>>> export {B as default} >>>>>> ``` >>>>>> >>>>>> ```js >>>>>> // --- Module C >>>>>> >>>>>> import A from './A' >>>>>> import B from './B' >>>>>> >>>>>> console.log('module C') >>>>>> let C >>>>>> >>>>>> export function initC(){ >>>>>> if (C) return >>>>>> >>>>>> console.log('initC!!!') >>>>>> >>>>>> C = class C { >>>>>> constructor() { >>>>>> // this may run later, after all three modules are >>>>>> evaluated, or >>>>>> // possibly never. >>>>>> console.log(A) >>>>>> console.log(B) >>>>>> } >>>>>> } >>>>>> } >>>>>> >>>>>> initC() >>>>>> >>>>>> export {C as default} >>>>>> ``` >>>>>> >>>>>> */#!/*JoePea >>>>>> >>>>>> On Thu, Aug 11, 2016 at 10:26 AM, Logan Smyth <loganfsmyth at gmail.com> >>>>>> wrote: >>>>>> >>>>>>> Keep in mind `let A = A;` is a TDZ error in any real ES6 >>>>>>> environment. >>>>>>> >>>>>>> The example I posted works properly with Babel's live-binding >>>>>>> implementation and should require less repetition. What were your thoughts >>>>>>> on it? >>>>>>> >>>>>>> On Thu, Aug 11, 2016 at 12:23 AM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>> >>>>>>>> Alright, so I believe I have found the solution. It is not possible >>>>>>>> to guarantee a certain module evaluation order, but using some clever (but >>>>>>>> tedious) conditional checking I believe the problem is solved (with two >>>>>>>> caveats listed after): >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Entrypoint >>>>>>>> import A from './A' >>>>>>>> console.log('Entrypoint', new A) >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module A >>>>>>>> >>>>>>>> import C from './C' >>>>>>>> import {setUpB} from './B' >>>>>>>> >>>>>>>> let A >>>>>>>> >>>>>>>> export >>>>>>>> function setUpA(C) { >>>>>>>> >>>>>>>> if (!A) { >>>>>>>> A = class A extends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> } >>>>>>>> >>>>>>>> } >>>>>>>> >>>>>>>> if (setUpA && C) setUpA(C) >>>>>>>> if (setUpB && C) setUpB(C) >>>>>>>> >>>>>>>> export {A as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module B >>>>>>>> >>>>>>>> import C from './C' >>>>>>>> import {setUpA} from './A' >>>>>>>> >>>>>>>> let B >>>>>>>> >>>>>>>> export >>>>>>>> function setUpB(C) { >>>>>>>> >>>>>>>> if (!B) { >>>>>>>> B = class B extends C { >>>>>>>> // ... >>>>>>>> } >>>>>>>> } >>>>>>>> >>>>>>>> } >>>>>>>> >>>>>>>> if (setUpA && C) setUpA(C) >>>>>>>> if (setUpB && C) setUpB(C) >>>>>>>> >>>>>>>> export {B as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> ```js >>>>>>>> // --- Module C >>>>>>>> >>>>>>>> import A, {setUpA} from './A' >>>>>>>> import B, {setUpB} from './B' >>>>>>>> >>>>>>>> class C { >>>>>>>> constructor() { >>>>>>>> // this may run later, after all three modules are >>>>>>>> evaluated, or >>>>>>>> // possibly never. >>>>>>>> console.log(A) >>>>>>>> console.log(B) >>>>>>>> } >>>>>>>> } >>>>>>>> >>>>>>>> if (setUpA && C) setUpA(C) >>>>>>>> if (setUpB && C) setUpB(C) >>>>>>>> >>>>>>>> export {C as default} >>>>>>>> ``` >>>>>>>> >>>>>>>> The caveat is that this fails in both Babel environments and in >>>>>>>> Rollup. For it to work in Babel environments, `let A` and `let B` have to >>>>>>>> be changed to `let A = A` and `let B = B`, as per the [fault in Babel's >>>>>>>> ES2015-to-CommonJS implementation](https://github >>>>>>>> .com/meteor/meteor/issues/7621#issuecomment-238992688) pointed out >>>>>>>> by Ben Newman. And it fails in Rollup because Rollup [isn't really creating >>>>>>>> live bindings](https://github.com/rollup/rollup/issues/845), which >>>>>>>> is fine in most cases, but doesn't work with these circular dependencies. >>>>>>>> The Rollup output does not create the C reference before it is ever used in >>>>>>>> the first pair of conditional checks, unlike what (I think) would happen >>>>>>>> with real live bindings (please correct me if wrong). To understand what I >>>>>>>> mean in the case of Rollup, just run `if (FOO) console.log(FOO)` in your >>>>>>>> console, and you'll get an error because FOO is not defined. Had the >>>>>>>> bindings been live, then FOO *would* be defined. >>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> */#!/*JoePea >>>>>>>> >>>>>>>> On Wed, Aug 10, 2016 at 5:04 PM, /#!/JoePea <joe at trusktr.io> wrote: >>>>>>>> >>>>>>>>> I found a solution that works in environments compiled by Babel, >>>>>>>>> using the [workaround suggested by Ben Newman]( >>>>>>>>> https://github.com/meteor/meteor/issues/7621#issueco >>>>>>>>> mment-238992688): >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module A >>>>>>>>> >>>>>>>>> import C from './C' >>>>>>>>> >>>>>>>>> let A = A // @benjamn's workaround applied >>>>>>>>> >>>>>>>>> export >>>>>>>>> function setUpA(C) { >>>>>>>>> >>>>>>>>> A = class A extends C { >>>>>>>>> // ... >>>>>>>>> } >>>>>>>>> >>>>>>>>> } >>>>>>>>> >>>>>>>>> export {A as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module B >>>>>>>>> >>>>>>>>> import C from './C' >>>>>>>>> >>>>>>>>> let B = B // @benjamn's workaround applied >>>>>>>>> >>>>>>>>> export >>>>>>>>> function setUpB(C) { >>>>>>>>> >>>>>>>>> B = class B extends C { >>>>>>>>> // ... >>>>>>>>> } >>>>>>>>> >>>>>>>>> } >>>>>>>>> >>>>>>>>> export {B as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Module C >>>>>>>>> >>>>>>>>> import A, {setUpA} from './A' >>>>>>>>> import B, {setUpB} from './B' >>>>>>>>> >>>>>>>>> let C = class C { >>>>>>>>> constructor() { >>>>>>>>> // this may run later, after all three modules are >>>>>>>>> evaluated, or >>>>>>>>> // possibly never. >>>>>>>>> console.log(A) >>>>>>>>> console.log(B) >>>>>>>>> } >>>>>>>>> } >>>>>>>>> >>>>>>>>> setUpA(C) >>>>>>>>> setUpB(C) >>>>>>>>> >>>>>>>>> export {C as default} >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> ```js >>>>>>>>> // --- Entrypoint >>>>>>>>> >>>>>>>>> import A from './A' >>>>>>>>> console.log('Entrypoint', new A) // runs the console.logs in the C >>>>>>>>> constructor. >>>>>>>>> ``` >>>>>>>>> >>>>>>>>> >>>>>>>>> Although that works in my environment which is compiled from ES6 >>>>>>>>> modules to CommonJS by Babel, it [doesn't work in Rollup.js]( >>>>>>>>> http://goo.gl/PXXBKI), and may not work in other ES6 module >>>>>>>>> implementations. >>>>>>>>> >>>>>>>>> Is there some solution that will theoretically work in any ES6 >>>>>>>>> module environment? >>>>>>>>> >>>>>>>>> */#!/*JoePea >>>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> _______________________________________________ >>>>>>>> es-discuss mailing list >>>>>>>> es-discuss at mozilla.org >>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>> >>>>>>>> >>>>>>> >>>>>> >>>>> >>>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20161009/8305f877/attachment-0001.html>
I don't understand what|CircularDep|and|NonCircularDep|refer to. To me, all the modules in the question contain some form of circular dependencies. Can you please post an answer in terms of|A|,|B|,|C|as defined in stackoverflow.com/q/38841469/14731 ?
Thank you, Gili
Hi Joe, I don't understand what|CircularDep|and|NonCircularDep|refer to. To me, all the modules in the question contain some form of circular dependencies. Can you please post an answer in terms of|A|,|B|,|C|as defined in http://stackoverflow.com/q/38841469/14731 ? Thank you, Gili -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20170309/ae1a4d3c/attachment.html>
I posted the answer: stackoverflow.com/a/42704874/454780
Hi Gili, I posted the answer: http://stackoverflow.com/a/42704874/454780 */#!/*JoePea On Thu, Mar 9, 2017 at 9:18 AM, cowwoc <cowwoc at bbs.darktech.org> wrote: > Hi Joe, > > I don't understand what CircularDepand NonCircularDep refer to. To me, > all the modules in the question contain some form of circular dependencies. > Can you please post an answer in terms of A, B, C as defined in > http://stackoverflow.com/q/38841469/14731 ? > > Thank you, > Gili > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20170309/66165312/attachment.html>
How about applying Inversion of Control by implementing Dependency Injection using Constructor injection? Just make your C constructor take two parameters A and B like this:
// --- Module A
import C from './C';
export default class A extends C {
// ...
}
// --- Module B
import C from './C';
export default class B extends C {
// ...
}
// --- Module C
export default class C {
constructor(A, B) {
// this may run later, after all three modules are evaluated, or
// possibly never.
console.log(A);
console.log(B);
}
}
// --- Entrypoint
import A from './A';
import B from './B';
import C from './C';
const c = new C(A, B);
console.log('Entrypoint', C, c);
document.getElementById('out').textContent = 'Entrypoint ' + C + ' ' + c;
www.webpackbin.com/bins/-KlDeP9Rb60MehsCMa8u
en.wikipedia.org/wiki/Dependency_injection#Constructor_injection
en.wikipedia.org/wiki/Inversion_of_control#Implementation_techniques
How about applying Inversion of Control by implementing Dependency Injection using Constructor injection? Just make your C constructor take two parameters A and B like this: ```js // --- Module A import C from './C'; export default class A extends C { // ... } // --- Module B import C from './C'; export default class B extends C { // ... } // --- Module C export default class C { constructor(A, B) { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A); console.log(B); } } // --- Entrypoint import A from './A'; import B from './B'; import C from './C'; const c = new C(A, B); console.log('Entrypoint', C, c); document.getElementById('out').textContent = 'Entrypoint ' + C + ' ' + c; ``` https://www.webpackbin.com/bins/-KlDeP9Rb60MehsCMa8u https://en.wikipedia.org/wiki/Dependency_injection#Constructor_injection https://en.wikipedia.org/wiki/Inversion_of_control#Implementation_techniques
Constructor injection should work as expected in any environment compatible and compliant with the ES6 and module specs. Or any functional environment with a module system.
Imho, it is the simplest and easiest to understand solution as well, it only removes code and adds two parameters. Thus expressing exactly what part is dependent on A and B in a parameterized reusable way, without any static cyclic dependencies. And without any complexity in the module initialization / resolution / evaluation.
Just reading the code once allows me to think about the entire flow, without keeping more than three concept in mind at once. Essentially allowing me to predict the entire execution of the program, without even running it.
Constructor injection should work as expected in any environment compatible and compliant with the ES6 and module specs. Or any functional environment with a module system. Imho, it is the simplest and easiest to understand solution as well, it only removes code and adds two parameters. Thus expressing exactly what part is dependent on A and B in a parameterized reusable way, without any static cyclic dependencies. And without any complexity in the module initialization / resolution / evaluation. Just reading the code once allows me to think about the entire flow, without keeping more than three concept in mind at once. Essentially allowing me to predict the entire execution of the program, without even running it.
I have the very basic problem detailed at stackoverflow.com/questions/38841469, but thought I'd bring it up here because it makes me wonder about the ES6 Module system.
Why is it that the body of module C is not evaluated before the bodies of modules A and B? I'm just wondering, because if that were the case, then it would work and the entrypoint would eventually be evaluated without errors.
A possibility could be that maybe it should work, and that my ES6 module environment simply doesn't handle it as it should? (But I'm not familiar enough with the spec, so that's why I'm not sure about that.)
These are the modules, posted here for convenience:
// --- Entrypoint import A from './app/A' console.log('Entrypoint', A)
// --- Module A import C from './C' console.log('Module A', C) class A extends C { // ... } export {A as default}
// --- Module B import C from './C' console.log('Module B', C) class B extends C { // ... } export {B as default}
// --- Module C import A from './A' import B from './B' console.log('Module C', A, B) class C { constructor() { // this may run later, after all three modules are evaluated, or // possibly never. console.log(A) console.log(B) } }