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

