The solution proposed in the linked article does not eliminate circular dependencies – it merely avoids any disastrous effects they may cause by carefully controlling the load order. To quote:
The reason that this solves our problem is: we now have full control
over the module loading order. Whatever the import order in
internal.js is, will be our module loading order.
Circular dependencies are only problematic and therefore the module load order is only really important if you have modules that evaluate their imports immediately as they are loaded – i.e. the imported values are used in the main body of the module:
// Foo.js
import { Bar } from "./Bar"
export class Foo {
getBarClass() {
return Bar;
}
}
// Bar.js
import { Foo } from "./Foo"
console.log(Foo.name); // Foo eval'ed on load :(
export const fooClass = Foo; // Foo eval'ed on load :(
export class Bar extends Foo { // Foo eval'ed on load :(
static fooClass = Foo; // Foo eval'ed on load :(
fooClass = Foo; // Eval'ed only on new Bar()
static getFooClass() {
return Foo; // Eval'ed only on Bar.getFooClass()
}
getFooClass() {
return Foo; // Eval'ed only on someBar.getFooClass()
}
}
In this example Foo.js does not access Bar at load time, only later when getBarClass is called. On the other hand, Bar.js does access the imported Foo multiple times at load time. Therefore, Bar.js has to be loaded first.
Circular dependencies cease to be an issue once you no longer access imports immediately at load time:
// execute in next tick
setImmediate(() => console.log(Foo.name));
// wrap in function
export const getFooClass = () => Foo;
// wrap in function
export const getBarClass = () => {
return class Bar extends Foo {
static fooClass = Foo;
fooClass = Foo;
};
};
Of course these solutions for delayed evaluation look rather ugly so in the end you may want to go with the carefully coordinated load order after all.