-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
Requiring a dynamically added dependency fails when the package has no index.js
file
#44663
Comments
I found a working workaround: |
@DavidWeiss2 your workaround doesn't seem to work on my PC |
Can you please increase the timeout time? In my opinion the bug root cause is an i/o opertion of the write. And it worth a shot |
Even an increase to 10 seconds doesn't help. |
@zkochan I uploaded another workaround at the same fork. |
Just published an npm package that solves this issue: |
This workaround will work only on direct dependencies, not on subdependencies. I guess we could hook into |
Alright, this also works: try {
console.log(require("pkg"));
} catch (err) {
console.error("failed require from package json");
const data = require('pkg/package.json')
console.log("this works", require('pkg/' + data.main));
} We can use it to patch But of course it still needs to be fixed in Node.js. |
Why do you use mkdir repro && cd repro
echo 'try { console.log(require("pkg")) } catch { console.log("first try failed") }' > entry.js
echo 'const fs = require("node:fs");' >> entry.js
echo 'fs.mkdirSync("node_modules/pkg/lib", { recursive:true });' >> entry.js
echo 'fs.writeFileSync("node_modules/pkg/lib/main.js", "module.exports = `lib/main was loaded`");' >> entry.js
echo 'fs.writeFileSync("node_modules/pkg/package.json", JSON.stringify({main:"lib/main.js"}));' >> entry.js
echo 'try { console.log(require("pkg")) } catch { console.log("second try failed") }' >> entry.js
node entry.js
cd .. && rm -r repro If I paste the above in my terminal, it outputs:
I'm going to close this as it doesn't seem to be a bug in Node.js, don't hesitate to re-open if you think I missed something or continue the discussion if you have more questions. |
it doesn't matter whether I use child process or use
I can't reopen it. Your code also doesn't work if I move require two line below: const fs = require("node:fs");
fs.mkdirSync("node_modules/pkg/lib", { recursive:true });
try { console.log(require("pkg")) } catch { console.log("first try failed") }
fs.writeFileSync("node_modules/pkg/lib/main.js", "module.exports = `lib/main was loaded`");
fs.writeFileSync("node_modules/pkg/package.json", JSON.stringify({main:"lib/main.js"}));
try { console.log(require("pkg")) } catch { console.log("second try failed") } |
Ah right, the issue is that Node.js doesn't re-check the FS if there was no
I'm not sure it's worth fixing, having to re-request the FS and re-parse the |
I agree - has the use case of dynamically creating requireable files been one node has ever intentionally supported? |
@aduh95 , if it's not worth fixing, it would be great to have the option to "clear" this kind-of cache of the non-found package.json files. (similar to the ability to clear |
It works inconsistently now. If I dynamically create a new package with an This definitely looks like worth fixing to me. It should be cheap to fix as you only need to reset cache and retry on a |
That would certainly be a reasonable option (and the one I would prefer personally), but given it's been this way for 13 years old, it's just not worth to break at this point IMHO. FWIW this behavior is already deprecated for ESM (see DEP0151), and CJS just has to live with it at this point.
Or we could state that dynamically modifying files in |
I am not sure why you are calling it "dynamically modified files". No files are modified. The files don't exist at the time of the first require. Hence, there is no reason to cache anything. Then the package is added to node_modules and it is there when require is executed again. So, it is not like there was a |
@aduh95 it's not thirteen years old. IIUC, the specified cache is So my guess is 2-3 years old. And my guess is that the code does work in Node v10, which is the earliest Node.js that does not have full ESM functionality backported to it. |
Unfortunately, removing this optimization would probably have significant performance disadvantages. |
@zkochan do you care about the same scenario, but in ESM? There the cache is entirely hidden and by design, so only a loader would help here. |
When I opened a PR to not keep the stat cache on failure it was rejected in the past #31921 |
see #31803 for prior discussion |
This stopped working in Node.js v7.1.0. The last working version is Node.js v6.17.1
Yes. In bit we have extensions that are installed dynamically. We are in big trouble if this won't work. If this won't be fixed then we either need a way to clear the cache or to hook into require and add our own fix. For now, I was able to add a workaround by hooking into the require function using the fix suggested by @DavidWeiss2. You do support dynamic imports. Even with esm you support |
Dynamic requires/imports are for a non-static specifier imo, not for a non-static module map. |
This is unfortunately ancient and hardly a regression at this point. I guess the use case you want to support was never "supported" by Node.js - there were no tests to cover it. It just "worked" because Node.js is exceptionally malleable and folks could do all sorts of things with it. Unfortunately, ESM removed some of that flexibility. What you want to achieve could be easily implemented for ESM dependencies using loader hooks. |
@mcollina I'd love to submit a fix for this, with guidance. Is a new API needed to clear this cache or can we hook on |
@matthewp I'm not that familiar with the commonjs loader to guide you, but I'll be very happy to review any intermediate step before marking the PR ready. I'm also happy to jump on a call. |
There's several caches in the CJS loader, how do people feel about a |
My only opinion on that front would be "rather |
I have hesitations about this. We can’t and possibly won’t ever be able to clear the ESM cache, because the modules are loaded into V8 and V8 doesn’t provide an ability to unload them as far as I know. The CommonJS and ESM caches overlap somewhat, if I remember correctly; I think this “ Another way to solve the original problem is to create a tool that analyzes the Node application to be run, tracing all the |
This feature is going to be commonjs only. I don't think we will ever be able to support it in ESM for the limitations you have described. Specifically, the cache to clear is - which is very limited to commonjs: node/lib/internal/modules/cjs/loader.js Line 157 in 86088ab
Modules that need this fix are downloaded millions of times per week, e.g. import-fresh. |
Yes, this is specifically about the CJS cache. Even more specifically the Actually I think this bug can be fixed without a new API to clear the cache. @giltayar said:
We don't need to remove this optimization, we only need to remove it from caching false-reads. Specifically we just need to remove this line: node/lib/internal/modules/cjs/loader.js Line 337 in 070e111
In this case when a package.json is found it continues to be used from the cache. Only if one is not found would it do another fs read. This seems fine from a performance perspective. The commit where this was added 4396beb was not done for performance reasons, so I'm thinking this is an incidental change. |
The ESM loader also uses the same
I don't share your optimism, it would force a FS lookup for every |
Stepping back for a minute from implementation details, I’d like some consideration given to why this needs to be part of core. In #44663 (comment) I theorized a potential way to implement this “install missing dependencies automatically” feature request via an external script that could be run before the app. Is there some reason why this functionality can’t be achieved without changes to core? Yes the external script would require restarting the Node process instead of just clearing the cache, and presumably clearing the cache would be slightly faster, but anyone who’s trying to install packages on demand presumably isn’t also trying to maximize performance. The external script approach would also have the benefit that it would work for both CommonJS and ESM. |
@GeoffreyBooth Aside from that being a very heavy workaround, it doesn't fix the use-case which is dynamic requires. Bundlers do not support truly dynamic requires either.
That cache is not exported from this module. Do you mean that it's used indirectly because it somehow uses the CJS loader internally? I'll take your word for it on that then, we can do an explicit
Given that it was added in a PR not related to performance I think this is an edge-case that people have lived with for a long time. |
I view that as an implementation detail, to achieve the broader goal of automatic installation of missing dependencies. My point is that you can achieve this end goal in other ways. |
It is exposed through
Before we introduced |
@GeoffreyBooth There's always a workaround, no doubt. My use-case is not automatic installation of missing dependencies, however. My use-case is allowing a user to install a dependency after they have started a Node.js server without having to kill the process and start it again. |
The issue can be boiled down into the node.js failing to update its cache of |
Version
v7.1.0 and newer (last working version was v6.17.1)
Platform
Linux
Subsystem
No response
What steps will reproduce the bug?
Require a dependency that doesn't exist. Catch the error. Dynamically add the missing package to the node_modules. It should not contain an
index.js
file but should use a custom entry point instead. Require the now not missing package again.Expected result: the modules is loaded.
Actual result: module not found error is thrown.
I have create a repro repo: https://github.com/zkochan/require-issue
How often does it reproduce? Is there a required condition?
No response
What is the expected behavior?
No response
What do you see instead?
On the second require the module is loaded
Additional information
cc @GiladShoham @davidfirst
The text was updated successfully, but these errors were encountered: