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

classic Classic list List threaded Threaded
41 messages Options
123
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
I have the very basic problem detailed at http://stackoverflow.com/questions/38841469, but thought I'd bring it up here because it makes me wonder about the ES6 Module system.

Why is it that the body of module C is not evaluated before the bodies of modules A and B? I'm just wondering, because if that were the case, then it would work and the entrypoint would eventually be evaluated without errors.

A possibility could be that maybe it *should* work, and that my ES6 module environment simply doesn't handle it as it should? (But I'm not familiar enough with the spec, so that's why I'm not sure about that.) 

These are the modules, posted here for convenience:

```js
// --- Entrypoint

import A from './app/A'
console.log('Entrypoint', A)
```

```js
// --- Module A

import C from './C'

console.log('Module A', C)

class A extends C {
    // ...
}

export {A as default}
```

```js
// --- Module B

import C from './C'

console.log('Module B', C)

class B extends C {
    // ...
}

export {B as default}
```

```js
// --- Module C

import A from './A'
import B from './B'

console.log('Module C', A, B)

class C {
    constructor() {
        // this may run later, after all three modules are evaluated, or
        // possibly never.
        console.log(A)
        console.log(B)
    }
}
```

/#!/JoePea

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
Oops, I forgot the export in module C:

```js
// --- Module C

import A from './A'
import B from './B'

console.log('Module C', A, B)

class C {
    constructor() {
        // this may run later, after all three modules are evaluated, or
        // possibly never.
        console.log(A)
        console.log(B)
    }
}

export {C as default} // export!
```

/#!/JoePea

On Tue, Aug 9, 2016 at 1:03 PM, /#!/JoePea <[hidden email]> wrote:
I have the very basic problem detailed at http://stackoverflow.com/questions/38841469, but thought I'd bring it up here because it makes me wonder about the ES6 Module system.

Why is it that the body of module C is not evaluated before the bodies of modules A and B? I'm just wondering, because if that were the case, then it would work and the entrypoint would eventually be evaluated without errors.

A possibility could be that maybe it *should* work, and that my ES6 module environment simply doesn't handle it as it should? (But I'm not familiar enough with the spec, so that's why I'm not sure about that.) 

These are the modules, posted here for convenience:

```js
// --- Entrypoint

import A from './app/A'
console.log('Entrypoint', A)
```

```js
// --- Module A

import C from './C'

console.log('Module A', C)

class A extends C {
    // ...
}

export {A as default}
```

```js
// --- Module B

import C from './C'

console.log('Module B', C)

class B extends C {
    // ...
}

export {B as default}
```

```js
// --- Module C

import A from './A'
import B from './B'

console.log('Module C', A, B)

class C {
    constructor() {
        // this may run later, after all three modules are evaluated, or
        // possibly never.
        console.log(A)
        console.log(B)
    }
}
```

/#!/JoePea


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
For reference, I'm trying this in Meteor, in which case the first module to be evaluated is module B, but that means that `C` is `undefined` during evaluation of `B`, so we get an error.

/#!/JoePea

On Tue, Aug 9, 2016 at 1:05 PM, /#!/JoePea <[hidden email]> wrote:
Oops, I forgot the export in module C:

```js
// --- Module C

import A from './A'
import B from './B'

console.log('Module C', A, B)

class C {
    constructor() {
        // this may run later, after all three modules are evaluated, or
        // possibly never.
        console.log(A)
        console.log(B)
    }
}

export {C as default} // export!
```

/#!/JoePea

On Tue, Aug 9, 2016 at 1:03 PM, /#!/JoePea <[hidden email]> wrote:
I have the very basic problem detailed at http://stackoverflow.com/questions/38841469, but thought I'd bring it up here because it makes me wonder about the ES6 Module system.

Why is it that the body of module C is not evaluated before the bodies of modules A and B? I'm just wondering, because if that were the case, then it would work and the entrypoint would eventually be evaluated without errors.

A possibility could be that maybe it *should* work, and that my ES6 module environment simply doesn't handle it as it should? (But I'm not familiar enough with the spec, so that's why I'm not sure about that.) 

These are the modules, posted here for convenience:

```js
// --- Entrypoint

import A from './app/A'
console.log('Entrypoint', A)
```

```js
// --- Module A

import C from './C'

console.log('Module A', C)

class A extends C {
    // ...
}

export {A as default}
```

```js
// --- Module B

import C from './C'

console.log('Module B', C)

class B extends C {
    // ...
}

export {B as default}
```

```js
// --- Module C

import A from './A'
import B from './B'

console.log('Module C', A, B)

class C {
    constructor() {
        // this may run later, after all three modules are evaluated, or
        // possibly never.
        console.log(A)
        console.log(B)
    }
}
```

/#!/JoePea



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Jason Orendorff
In reply to this post by /#!/JoePea
On Tue, Aug 9, 2016 at 3:03 PM, /#!/JoePea <[hidden email]> wrote:
Why is it that the body of module C is not evaluated before the bodies of modules A and B?

The module system *does* execute all a module's dependencies before executing that module, *except* in cases like this where there are cycles in the dependency graph and that requirement is therefore impossible to satisfy for all modules simultaneously.

-j


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
Hi Jason,

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).

- Joe

/#!/JoePea

On Tue, Aug 9, 2016 at 2:04 PM, Jason Orendorff <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 3:03 PM, /#!/JoePea <[hidden email]> wrote:
Why is it that the body of module C is not evaluated before the bodies of modules A and B?

The module system *does* execute all a module's dependencies before executing that module, *except* in cases like this where there are cycles in the dependency graph and that requirement is therefore impossible to satisfy for all modules simultaneously.

-j



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Tab Atkins Jr.
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

John Lenz-4
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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss





_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Isiah Meadows-2

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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Isiah Meadows-2
In reply to this post by /#!/JoePea

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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
In reply to this post by /#!/JoePea
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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Isiah Meadows-2
In reply to this post by Isiah Meadows-2

To clarify, Webpack and friends translate ES6 modules to CommonJS modules pretty naïvely, so the bindings won't get translated correctly (CommonJS modules export values, not read-only bindings). Rollup actually handles the exports correctly as static bindings instead. So that's why I recommended testing it in Rollup.


On Wed, Aug 10, 2016, 15:40 Isiah Meadows <[hidden email]> wrote:

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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Bradley Meck
In reply to this post by /#!/JoePea
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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

/#!/JoePea
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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss





_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

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

Bradley Meck

---------- Forwarded message ----------
From: Bradley Meck <[hidden email]>
Date: Wed, Aug 10, 2016 at 3:05 PM
Subject: Re: How to solve this basic ES6-module circular dependency problem?
To: /#!/JoePea <[hidden email]>


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?

The graph is traversed in order of declaration in source text, depth first. https://github.com/trusktr/meteor/blob/issue-7621/meteor-app/client/main.js#L23 will be evaluated only after all of https://github.com/trusktr/meteor/blob/issue-7621/meteor-app/client/main.js#L22 has been evaluated.

* A will never load via before C, because C loads A first. (confusing wording since we have circular stuff here)
* A attempting to load C when C has already begun evaluating results in the short circuit at 15.2.1.16.5.4.

On Wed, Aug 10, 2016 at 2:58 PM, /#!/JoePea <[hidden email]> wrote:
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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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 <[hidden email]> 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. <[hidden email]> wrote:
On Tue, Aug 9, 2016 at 4:00 PM, /#!/JoePea <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss





_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss




_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
123