-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rollup does not preserve order of imports after commonjs #3816
Comments
I do not think Rollup is messing up here but the commonjs plugin, but one would need to look deeper. There are situations where Rollup can mess up import order, but this is only possible in the presence of chunks or external dependencies, neither of which seems to be the case here. The first step would be to log all modules as they are passed to Rollup, e.g. by adding a plugin {
transform(code, id) {
console.log('===>', id);
console.log(code);
}
} and the noting all imports to plot the canonical execution order. Then it should become clear what is going wrong here. |
The reason is indeed simple:
|
So the correct execution order was indeed
which is what Rollup did. This is indeed more of a fundamental issue with the commonjs plugin here because it does not account for the fact that dynamic modules can have dependencies themselves. Not sure if there is an easy fix. |
So basically when it
It translates it into this:
So as far as rollup is concerned, there are multiple Now I'm hitting my head against the wall, trying to figure out a way around this. |
Not sure. So as I understand is, there are several approaches:
Nothing ideal yet I guess. |
@danielgindi I have thought some more about this and I think I know a way forward. So as I understand it what the
I am not 100% sure about the second but if you completely forego the notion that you want to couple this feature with conditional code execution, then it can be made much simpler and remove a lot of the current cognitive complexity out of the commonjs plugin. The problem is as we saw that conditional execution does not work anyway as transitive dependencies will still be executed on startup, messing up the registration code. This is something to tackle separately IMO, and I would have some vague ideas where to start. (By the way, I just stumbled about the issues when bundling logform myself, which is the example you put into the docs. My fix was to use undocumented deep imports, but I guess a direct solution would be better). So assume you have commonjs({
dynamicRequireTargets: [
'foo', 'bar'
],
}); Then we add the following virtual module:
So at the moment, this would be translated to immediate synchronous require calls to 'foo' and 'bar' despite me putting them into functions. But if we implement general conditional require support in this plugin in the future, this feature will immediately benefit from this. Note that this should be a CommonJS file so that we do not get any unwanted interop code. Then we can change the dynamic commonjs helpers like this: import DYNAMIC_REQUIRE_CACHE from '\0commonjs-dynamic-packages';
// get rid of DYNAMIC_REQUIRE_CACHE and DYNAMIC_REQUIRE_LOADERS below
// ...
export function commonjsRequire (path, originalModuleDir) {
const shouldTryNodeModules = isPossibleNodeModulesPath(path);
path = normalize(path);
let relPath;
while (true) {
if (!shouldTryNodeModules) {
relPath = originalModuleDir ? normalize(originalModuleDir + '/' + path) : path;
} else if (originalModuleDir) {
relPath = normalize(originalModuleDir + '/node_modules/' + path);
} else {
relPath = normalize(join('node_modules', path));
}
for (let extensionIndex = 0; extensionIndex < CHECKED_EXTENSIONS.length; extensionIndex++) {
const resolvedPath = relPath + CHECKED_EXTENSIONS[extensionIndex];
let loader = DYNAMIC_REQUIRE_CACHE[resolvedPath];
if (loader) return loader().exports;
}
if (!shouldTryNodeModules) break;
const nextDir = normalize(originalModuleDir + '/..');
if (nextDir === originalModuleDir) break;
originalModuleDir = nextDir;
}
return require(path);
} and that's all. We do not need to cache the exports of each module because that is taken care of elsewhere. |
The second one is actually a side effect. So the cache is part of what allows that. There's a small algorithm in there that enables it in general- by first putting the The fact that modules will only be executed once is a sideeffect here, and is not 100%. It actually works like in node, where one could actually access the cache, remove a module from cache and cause it to reload! So a commonjs module that takes advantage of nodejs module system to the fullest extent- will work the same here.
Yeah it's one of the most annoying modules in the npm ecosystem. I opened an issue over there but got ignored. If I understand you correctly, you are suggesting to create a single map at the top, so That's a nice idea, except commonjs translate normal I also had a more complex idea on how to solve this. Basically making any But now I think a faster route there would be a variation on what you suggested! As a side note- in general the fact that an |
@lukastaegert Anyway, it suddenly hit me that the fact the imports are sneaking in between should not be an issue as if they caused an issue then it hints about a circular dependency... So I deem this a non-issue, a false alarm, and I'm closing this. Just for the sake of developers looking for solutions about specific modules with circular dependencies, I'll list a few I know about here:
|
We are finalizing a new version of the commonjs plugin in rollup/plugins#1038. It is pre-published as |
Expected Behavior
All
commonjsRegister
calls should be at the top.The
commonjs
plugin generates them at the top, and it should stay that way.In cases of
import from 'whatever'
, the import is clearly meant for causing a side effect, and thus it's order of execution matters.(Of course it could be also in normal imports, but this case is very explicit and well known).
Actual Behavior
The order of imports generated by the
commonjs
plugin is not preserved, and other modules appear in between.The result in some cases is that there's a call to
commonjsRequire
before the dynamic modules have been registered, so they it can't find/generate the module during thecommonjsRequire
call.In this specific REPL you can see that between the
commonjsRegister(...internal/wrapAsync)
andcommonjsRegister(...asyncify)
, there's one piece of codevar setImmediate_1 = createCommonjsModule(...)
, which is extra weird since this variable is not in use yet, so why bump it to that position on top?I know that rollup tends to manage the order of things by itself for the sake of optimization,
but we need to think of a way to at least specify cases where the user (or a plugin) requires a steady order of imports.
And perhaps detect those "side effect" imports and handle those automatically.
The text was updated successfully, but these errors were encountered: