How to solve this basic ES6-module circular dependency problem?
/#!/JoePea
joe at trusktr.io
Wed Aug 10 19:41:13 UTC 2016
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 <joe at trusktr.io> 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 <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/8c700f3c/attachment-0001.html>
More information about the es-discuss
mailing list