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 <[email protected]> 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 <[email protected]> > 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 <[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

