-
Notifications
You must be signed in to change notification settings - Fork 29.9k
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
Worker eval as ES Module #30682
Comments
Adding |
Can I use Es6 module in Worker by using |
@nodejs/modules-active-members |
@mko-io if your file would be interpreted as a module when run with |
I hope we'll support import { Worker } from 'worker_threads';
new Worker(
`
import { join } from 'path';
console.log(join('hello', 'world'));
`,
{ eval: true, type: 'module' },
); (Not working code, just as a possible example of what it would look like in node.) |
If we do add a |
I assume it should only Since we don't support bare specifiers for workers and we don't support conditional mappings of effectively "absolute URLs", I assume that |
Do workers already support the package.json Perhaps eval can be supported via either |
I think we should try to match the web worker API, which uses |
The question is - why though? |
why work towards compat with other runtimes? it's annoying and confusing when there's a difference. we also do try to work in compat where possible, like supporting on{event} properties. |
As mentioned |
I guess one question is, could Anyway my point is, if theoretically |
Also this compat isn't theoretical, i've been able to run wasm threading demos in node just by creating globals of MessagePort and Worker from worker_threads. |
Here is my best shot at a single const isNode = typeof process === 'object' && process.versions && process.versions.node;
// Give me Top-Level Await!
Promise.resolve()
.then(async () => {
const workerThreads = isNode && await import('worker_threads');
const fileURLToPath = isNode && (await import('url')).fileURLToPath;
const isMainThread = isNode ? workerThreads.isMainThread : typeof WorkerGlobalScope === 'undefined' || self instanceof WorkerGlobalScope === false;
if (isMainThread) {
const { Worker } = isNode ? workerThreads : window;
const worker = new Worker(isNode ? fileURLToPath(import.meta.url) : import.meta.url, { type: 'module' });
worker.postMessage('hello');
if (isNode)
worker.on('message', onMessage);
else
worker.addEventListener('message', onMessage);
function onMessage (e) {
console.log(isNode ? e : e.data);
}
}
// Worker
else {
if (isNode)
workerThreads.parentPort.on('message', onMessage);
else
addEventListener('message', onMessage);
function onMessage(e) {
(isNode ? workerThreads.parentPort : self).postMessage((isNode ? e : e.data) + ' world');
}
}
}); The story works, but it took me at least half an hour to get all the details right. All in all it needs 9 branches to handle platform checks. Supporting This is what I mean when I say that arguing for |
@guybedford I wouldn’t call it a compatibility issue so much as a familiarity issue – the |
@addaleax I am only arguing against the points made by @devsnek and @GeoffreyBooth that we have to use The reason I'm making this argument is because Node.js modules have a way to know the module type of every single module, which we've carefully worked out. We specially don't allow inline type information for the loader, so I don't think we should make workers an exception here. If we were to support "type": "module" for worker instantiation this introduces a new out-of-band mechanism that further complicates module interpretation, whereas the current techniques of module detection currently work perfectly fine for worker threads. This is why my suggestion is to rely on the content-type for data URIs like we do in the module loader, or to have something like |
using |
Allows eval of ESM string. Fixes: nodejs#30682
Related #34584 |
I ran into this issue today and there is a "workaround" to be able to execute ESM in worker as explained in #36985 (comment) import { Worker } from "node:worker_threads"
const worker = new Worker(new URL('data:text/javascript,import fs from "fs"')); However the code inside the data url throws when it contains relative urls. import { Worker } from 'node:worker_threads'
const worker = new Worker(new URL('data:text/javascript,import "./file.mjs"')); Ideally I could do something as below: new Worker(`
import value from "./file.js";
console.log(value);
`,
{ eval: true, type: 'module', url: import.meta.url },
); I understand it's not possible today but is there something I can do with the API offered by Node.js? |
I don't know if that fits your use-case, but you should be able to do the following: const toDataUrl = js => new URL(`data:text/javascript,${encodeURIComponent(js)}`);
const toAbsoluteUrl = relativeUrl => JSON.stringify(new URL(relativeUrl, import.meta.url));
new Worker(toDataUrl(`
import value from ${toAbsoluteUrl('./file.js')};
console.log(value);
`)
); |
Converting to absolute urls would work indeed! Good enough for me. Edit: Actually no 😬 . There is several other cases where node throw new Worker(toDataUrl(`
import value from "leftpad";
console.log(value);
`)
); ☝️ In the code above |
I guess I could resolve all specifiers with import.meta.resolve but it's experimental so I would be forced to put |
It also doesn’t work properly, even with the flag. |
If ESM is unavoidable, it may be sensible to write the eval'd code to file first before calling the worker. // pseudo-code
import {writeFile} from 'fs/promises';
await writeFile(
'./module-eval.js',
`import something from 'somewhere';
console.log(something.doSomething());
`,
'utf8'
);
const worker = new Worker('./module-eval.js', {type: 'module'}); Note: This assumes top-level await support. |
We are now at 2023, and still no |
And of course those three engineers are going to invest their time to contribute a fix, or they are simply going to waste everyone’s time complaining it’s 2023? |
A lot reasons to complain about it being 2023, to be fair. |
new (require('worker_threads').Worker)(
`
import { join } from 'path';
console.log(join('hello', 'world'));
`,
{ eval: true, type: 'module' },
); import { Worker } from 'worker_threads';
new Worker(
`
import { join } from 'path';
console.log(join('hello', 'world'));
`,
{ eval: true, type: 'module' },
); AFAICT both these work, so I've closed this issue. LMK if I'm missing something. |
I'll have a look. Please don't close my issues unless there's an obvious reason (like a PR that fixes it) |
It also works without |
Not really, |
I know it's ignored. What I find unexpected is that module detection happens here (in the eval path) |
I don't think it's unexpected (at least I don't find it surprinsing), string inputs being affected was mentioned in the original PR that added the flag (#50096 (comment)). However, what I find surprising is that the snippet is also broken if you pass $ node <<'EOF'
import { Worker } from 'worker_threads';
new Worker(
`
import { join } from 'path';
console.log(join('hello', 'world'));
`,
{ eval: true },
);
EOF
hello/world
$ node --input-type=module <<'EOF'
import { Worker } from 'worker_threads';
new Worker(
`
import { join } from 'path';
console.log(join('hello', 'world'));
`,
{ eval: true },
);
EOF
node:internal/event_target:1090
process.nextTick(() => { throw err; });
^
[worker eval]:2
import { join } from 'path';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at makeContextifyScript (node:internal/vm:185:14)
at node:internal/process/execution:107:22
at [worker eval]-wrapper:6:24
at runScript (node:internal/process/execution:101:62)
at evalScript (node:internal/process/execution:136:3)
at MessagePort.<anonymous> (node:internal/main/worker_thread:167:9)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:816:20)
at MessagePort.<anonymous> (node:internal/per_context/messageport:23:28)
Node.js v23.0.0-pre |
Unless I missed it, there doesn't seem to be a way of creating a Worker from an ESM source string (with
eval: true
).Example that should be possible somehow:
Related: #21502
The text was updated successfully, but these errors were encountered: