You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A fairly classic problem with JS module evaluation order is that if there is a circular evaluation with a top-level reference to some value in one of the modules, we can get an error that occurs depending on which module was imported first.
For those not familiar or who need a reminder, consider the following example (using TS types for clarity):
importAbsolutePathfrom"./AbsolutePath.js";importRelativePathfrom"./RelativePath.js";// In this case Path is an abstract base class, all instances *must* either// be absolute or relative paths as other possibilities are impossible, if people// actually want to subclass they can subclass those classesexportdefaultabstractclassPath{staticisPath(value: any): value is Path{return #pathSegments inPath;}readonly #pathSegments: string[];constructor(pathSegments: string[]){// All actual instances need to be one of the concrete subclasses// further subclassing is allowed from thereif(!AbsolutePath.isPrototypeOf(new.target)&&!RelativePath.isPrototypeOf(new.target)){thrownewTypeError("Path cannot be instantiated directly, use either AbsolutePath or RelativePath instead");}}// ... the common methods between path kinds like .normalize(), .parentDir(), etc}
importPathfrom"./Path.js";exportdefaultclassAbsolutePathextendsPath{// ... absolute path specific stuff}
importPathfrom"./Path.js";exportdefaultclassRelativePathextendsPath{// ... relative path specific stuff}
Now, importing these modules can potentially fail with a reference error, in particular if you try importing from the Path file first then a reference error happens because the discovered evaluation order is:
AbsolutePath.js // Oops, this happens first because the depth-first search finds AbsolutePath.js as the first entry
RelativePath.js
Path.js
This can be seen in a tree diagram of the situation:
We can actually fix this by injecting an additional module that contains the implementation of Path and a new wrapper that just exports those definitions but imports the other modules as well.
In particular we move the contents of Path.js into a new file, say Path.impl.js, point those modules to this impl file, and add a new Path.js file which forces the evaluation order:
// Path.js// Import Path first so it evaluates firstimport"./Path.js";import"./AbsolutePath.js";import"./RelativePath.js";// Export the contents of Path so that this module looks like// the real path moduleexport*from"./Path.impl.js";export{defaultasPath}from"./Path.impl.js";
Again this can be more easily seen in a diagram:
However it's a bit unfortunate we need to inject the additional module and there can be confusion about which files subclasses should refer to (it technically doesn't matter in this case, as it won't change evaluation order, but the fact this is the case is fairly subtle).
But this proposal by allowing inline modules, can solve this by allowing us to instead of having to inject a Path.impl.js module, we could instead just put inside Path.js:
importPathImpl;// Ensure PathImpl evaluates before any subclassesimportAbsolutePathfrom"./AbsolutePath.js";importRelativePathfrom"./RelativePath.js";modulePathImpl{exportdefaultclassPath{// ...}}export*fromPathImpl;export{defaultasPath}fromPathImpl;
And voila, we've forced an evaluation for circular modules without any file hacks (or other hacks that involve exporting init functions and the like). The subclass files don't even need to know that there is a hidden module, instead they can just refer to Path.js without knowing anything about the extra injected module.
The text was updated successfully, but these errors were encountered:
A fairly classic problem with JS module evaluation order is that if there is a circular evaluation with a top-level reference to some value in one of the modules, we can get an error that occurs depending on which module was imported first.
For those not familiar or who need a reminder, consider the following example (using TS types for clarity):
Now, importing these modules can potentially fail with a reference error, in particular if you try importing from the
Path
file first then a reference error happens because the discovered evaluation order is:AbsolutePath.js
// Oops, this happens first because the depth-first search findsAbsolutePath.js
as the first entryRelativePath.js
Path.js
This can be seen in a tree diagram of the situation:
We can actually fix this by injecting an additional module that contains the implementation of
Path
and a new wrapper that just exports those definitions but imports the other modules as well.In particular we move the contents of
Path.js
into a new file, sayPath.impl.js
, point those modules to this impl file, and add a newPath.js
file which forces the evaluation order:Again this can be more easily seen in a diagram:
However it's a bit unfortunate we need to inject the additional module and there can be confusion about which files subclasses should refer to (it technically doesn't matter in this case, as it won't change evaluation order, but the fact this is the case is fairly subtle).
But this proposal by allowing inline modules, can solve this by allowing us to instead of having to inject a
Path.impl.js
module, we could instead just put insidePath.js
:And voila, we've forced an evaluation for circular modules without any file hacks (or other hacks that involve exporting init functions and the like). The subclass files don't even need to know that there is a hidden module, instead they can just refer to
Path.js
without knowing anything about the extra injected module.The text was updated successfully, but these errors were encountered: