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

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


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/923bf7a9/attachment-0001.html>


More information about the es-discuss mailing list