How to solve this basic ES6-module circular dependency problem?
/#!/JoePea
joe at trusktr.io
Wed Aug 10 19:58:39 UTC 2016
Aha, when using my setup functions, rollup.js tries to evaluate C first, in
which case the *hoisted* functions are available, but A is not declared
yet. I don't think that this would happen in real ES6 modules (no hoisting
like that).
*/#!/*JoePea
On Wed, Aug 10, 2016 at 12:55 PM, /#!/JoePea <joe at trusktr.io> wrote:
> Isiah, here's the [rollup.js result](http://goo.gl/jl1B8H) using my setup
> functions technique. When I paste the result in my console it complains
> that A is undefined inside the `setUpA` function, which seems odd. Here's
> the [result of my original code](http://goo.gl/cbjVOi) (similar to your
> example), and as you can see it will evaluate B first in which case C will
> be undefined and throw an error.
>
> Bradley, true, but C is also child of A, so it can also make sense to
> evaluate C before A. They are children of each other. In that case, what is
> the correct order of evaluation?
>
> */#!/*JoePea
>
> On Wed, Aug 10, 2016 at 12:47 PM, Bradley Meck <bradley.meck at gmail.com>
> wrote:
>
>> Please note that in https://tc39.github.io/ecma262/#sec-moduleevaluation
>> , Modules evaluate their children prior to evaluating
>> themselves (15.2.1.16.5.6.c) , C should never be evaluate before A or B
>> in this dep graph.
>>
>> On Wed, Aug 10, 2016 at 2:41 PM, /#!/JoePea <joe at trusktr.io> wrote:
>>
>>> 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
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>> _______________________________________________
>>> 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/1a564101/attachment-0001.html>
More information about the es-discuss
mailing list