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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> >>> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> >>>>>>> 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. < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>>> On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[email protected]> 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 >>>>>>>>> [email protected] >>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>>>> >>>>>>>> >>>>>>>> >>>>>>> >>>>>> >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> [email protected] >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>> >>> >> > > _______________________________________________ > es-discuss mailing list > [email protected] > https://mail.mozilla.org/listinfo/es-discuss > >
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

