How to solve this basic ES6-module circular dependency problem?

/#!/JoePea joe at trusktr.io
Wed Aug 10 19:38:09 UTC 2016


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>


More information about the es-discuss mailing list