-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
ESM loaders cannot be defined via Worker
option execArgv
in v20
#47747
Comments
Another side effect of ES loaders to a separate thread: updating This makes it significantly more challenging to have loaders that work for both esm and cjs, effectively breaking the esm on-ramp for those of us supporting both modes, since now you need to know ahead of time what the module type is (or write the transpiled source to another path and return a short circuit url to that instead), rather than being able to have a single loader capable of handling both. |
Verified that |
cc: @nodejs/loaders |
As stated in the ESM doc, async activity like Please try other methods, such as I believe this is not a dupe of #47615 because it specifically provides |
@JakobJingleheimer I've tried |
I’m not sure how would it work in the current design. There is (by design) one loader process for the whole Node.js process. In the example above, we are asking Node.js to start another one just for that specific thread. We need a new API to handle this case, something that:
|
What you are proposing seems like an explicit API. It seems like |
It's interesting because it doesn't work |
I don’t think of worker threads like child processes that can have their own Node flags. I feel like they should inherit whatever the parent process has, which is what @targos is describing here. |
That is the exact purpose of the |
Only in the sense that the way that chaining works for multiple invocations of Unless there’s some argument for why this should be considered a bug, I think we should just update the docs for https://nodejs.org/api/worker_threads.html#new-workerfilename-options to clarify that |
Worker
option execArgv
in v20
I guess my question is, are there actually technical reasons why this can't be implemented? |
I was about to ask this too. If it works from main, I would've expected it to work here too, but if not, I imagine it would be fairly simple to support from the esm worker 🤔 (unless we get stuck in Inception) |
In the current behavior, there’s a loaders thread and it works for both the main application thread and all worker threads (I think); within that loaders thread there’s shared scope that the loaders can use, such as to keep caches and such. If we support
In short, it’s a lot of added complexity for both Node and for loader authors, and for what? I don’t see the benefit. If you want to create a new process with different loaders, spawn a child process. |
@GeoffreyBooth I agree with your assessment with supporting IMHO, we need a new API to handle this case, something that:
|
What would a specific use case be? When would you want to do something that only a loader can do, that you wouldn’t want applied to the main application thread? Keep in mind there’s also the |
CLI tools can't really set Node.js options. |
Looks like the PR is #46826. I believe that would work for my use case. I still think |
What is your use case?
We’ve been discussing adding support for |
Using loaders in a worker without requiring users to specify CLI flags. Using a shebang is not really an option for me either, and ideally I don't want to spawn a child process just to hide the CLI flag. The fact that the loader thread is shared between workers and the main thread is not a problem for me. |
Can you explain a bit more? Is this a CLI tool? What does it do? Why does it create worker threads? |
I can't go into too much detail yet unfortunately, as it is for work and still unreleased. There is a CLI, but also an SDK. The loader itself is just used for hot module reloading, so I think |
If you find a good general solution for hot module reloading, please share it. The |
I will take a look at the |
That's wrong. Each worker thread has its own loaders thread. Without evidence of the contrary, I think we should consider that there is a bug somewhere. |
Found the bug: when a worker is created with main thread: |
✨ Edit: For those looking for a workaround in the meantime, @hi-ogawa has a working solution described here: vitest-dev/vitest#5757 (comment) @GeoffreyBooth thanks for championing this for us! I've just tried out the newly released v22.2.0 as you suggested, with the following simplified repro, in which the script tries to spawn a worker thread from its own file path: import { fileURLToPath } from "node:url";
import { Worker, isMainThread } from "node:worker_threads";
const THREAD_COUNT = 2;
if (isMainThread) {
console.log("Main thread running");
const workers = [...new Array(THREAD_COUNT)].map(() => {
const worker = new Worker(fileURLToPath(import.meta.url));
// Capture threadId immediately after spawning, while the worker is alive
const threadId = worker.threadId;
worker.on("exit", (exitCode: number) => {
console.log(`Worker ${threadId} exited with code ${exitCode}`);
});
return worker;
});
// The real application would post messages to the worker threads here
} else {
console.log("Doing work");
} Unfortunately I found that for both Node v22.1.0 and v22.2.0, executing this with
I was executing this in an ESM package (with I'm not familiar with the internals here but I'm wondering if this is a consequence of not being able to modify |
I'm getting Edit: For node v22.1.0 I also get |
Update I was missing something, you need to import the code that registers the hooks inside the worker. Once I did that the hooks started working. Am I missing something, or are single thread hooks not working in Node v22.2.0? According to the changelog, this landed in v22.2.0
However, I can't seem to make this work. I'm trying to add a loader for HTTP imports, using the example in the Nodejs docs (modified for HTTP instead of HTTPS). It works on the main thread but when I spawn a worker I get the following error.
I actually do want different loaders for the main thread and the worker thread. My use case is for React 19 server components. But I can probably workaround the single hooks thread limitation if it worked. |
I see most people here are trying to use
The workaround suggested here does work. However, when I pass conditions to my worker that does not seem to work with const worker = new TsWorker(new URL("./ClientRenderer.tsx", import.meta.url), {
execArgv: ["--conditions", "node"], // <-- doesn't seem to be working with suggested workaround.
}); |
If you are using
const worker = new Worker(require.resolve(workerPath), { execArgv: ["--require", "tsx/cjs"] }) Like if its help you 👍 the 'bug' was reported at privatenumber/tsx#354 (comment) too. |
Remove |
BTW, the |
This approach has |
This comment was marked as off-topic.
This comment was marked as off-topic.
FYI, #52706 landed in 22.2.0 as an attempt to have a single hooks thread that applies to all application threads, including the main thread. It has some issues that we’re addressing in follow-ups, but in 22.3.0 or 22.4.0 there should hopefully be a cleaner way to handle this, where if you register hooks (such as tsx) at startup then they will automatically apply to all worker threads. So |
I guess your take works but when running typescript ensure your tsconfig is properly set and you build with tsc before running node, I was able to run the above with this tsconfig, it is just the index.ts so this works. |
@proxybee it's true that is certainly worth considering, if it fits your use case, but then you wouldn't be using a loader at all, which is the topic being discussed here 🙂 |
There’s a discussion at nodejs/loaders#203 regarding whether registering hooks should apply automatically to |
I have been doing this while I wait for a solution: // package.json
{
"type": "module",
"imports": {
"#worker": {
"source": "./cmd/worker.ts",
"default": "./cmd/worker.js"
}
},
// ...
} // cmd/bin.ts
export function spawnWorker() {
let workerPath = url.fileURLToPath(import.meta.resolve('#worker'));
if (workerPath.endsWith('.ts')) {
return new Worker(`import('tsx/esm/api').then(({ register }) => { register(); import('${workerPath}') })`, { eval: true })
} else {
return new Worker(workerPath)
}
}
spawnWorker() Then run it with:
My long term plan here is to eventually replace |
I've tried this workaround but cannot get it working. Still stuck with this issue |
In which version(s) is this fixed? |
I tried on v20.17.0 and v22.9.0 and couldn't get it working. I think this ticket was closed prematurely. |
although @alshdavid already works, path aliases won't work. the tsx loader registered through eval doesn't pick up the paths defined in
|
Version
20.0.0 (tested on main at 36e4e3d too)
Platform
macOS but probably all platforms
Subsystem
esm
What steps will reproduce the bug?
The following works in Node 18 and 19, but not 20:
Run:
node main.mjs
. In Node 18 and 19, the exception in the loader is thrown. In Node 20 it is not. The problem is not unique to throwing. I haven't been able to seeconsole.log()
or any other indication that the loader is being called.How often does it reproduce? Is there a required condition?
100% of the time for me.
What is the expected behavior? Why is that the expected behavior?
I expect it to work as it did in earlier versions of Node. This is the expected behavior because right now it doesn't seem to work at all.
What do you see instead?
See the description of the bug above.
Additional information
Just a guess, but I'm assuming this is related to #44710.
The text was updated successfully, but these errors were encountered: