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 <[email protected]> wrote: > > ---------- Forwarded message ---------- > From: Bradley Meck <[email protected]> > Date: Wed, Aug 10, 2016 at 3:05 PM > Subject: Re: How to solve this basic ES6-module circular dependency > problem? > To: /#!/JoePea <[email protected]> > > > > 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 <[email protected]> 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 <[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/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 <[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-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 < >>>>>>> [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 >> >> > > > _______________________________________________ > 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

