How to solve this basic ES6-module circular dependency problem?
Isiah Meadows
isiahmeadows at gmail.com
Wed Aug 10 19:40:09 UTC 2016
What do you get out of Rollup.js (which actually implements the ES6 spec
with bindings, unlike Webpack, etc., which handle CommonJS modules)? You
should be able to run the bundled repro in Node, or you could inspect the
bundle yourself just as easily to see the execution order.
On Wed, Aug 10, 2016, 15:31 /#!/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/b5b909de/attachment-0001.html>
More information about the es-discuss
mailing list