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

Isiah Meadows isiahmeadows at gmail.com
Wed Aug 10 09:41:16 UTC 2016


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/8cda2318/attachment-0001.html>


More information about the es-discuss mailing list