Skip to content
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

vitest fails to load typescript worker code in node (but does so correctly in browser mode) #5757

Closed
6 tasks done
andreashuber69 opened this issue May 21, 2024 · 7 comments
Closed
6 tasks done

Comments

@andreashuber69
Copy link

andreashuber69 commented May 21, 2024

Describe the bug

As already asked here, I'm looking for a way to execute tests in node while using node:worker_threads. Equivalent code works just fine in browser mode.

Reproduction

git clone --branch original https://github.com/andreashuber69/vitest-repro.git
cd vitest-repro
npm install

(EDIT: modified to check out original code, to check out the workaround discussed below please use git clone https://github.com/andreashuber69/vitest-repro.git)

The following commands work as expected:

  • npm run test-browser
  • npm run test-node-js

The following doesn't work:

  • npm run test-node-ts

Apparently under node, test code isn't compiled to js first (as in browser mode), which is why the above produces the following error:

Error: Cannot find module '.../src/nodeWorker.js'

Attempts at importing tsx (see commented out code in ./src/nodeWorker.spec.ts) have failed with the following error:

TypeError: Unknown file extension ".ts" for .../src/nodeWorker.ts

(presented as a solution in this issue but apparently doesn't work any longer)

How can I use vitest with typescript code that uses node:worker_threads?

System Info

System:
    OS: Linux 6.8 Pop!_OS 22.04 LTS
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
    Memory: 24.06 GB / 31.20 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 20.12.1 - /usr/local/bin/node
    npm: 10.5.0 - /usr/local/bin/npm
  Browsers:
    Brave Browser: 125.1.66.110
    Chrome: 125.0.6422.60
  npmPackages:
    @vitest/browser: ^1.5.0 => 1.5.0 
    @vitest/coverage-istanbul: ^1.5.0 => 1.5.0 
    vitest: ^1.5.0 => 1.5.0

Used Package Manager

npm

Validations

@hi-ogawa
Copy link
Contributor

hi-ogawa commented May 22, 2024

Browser mode is quite different and it just happens that Vite's web worker support makes it work, but I don't think the same technique naturally transfers to node worker threads. It's unlikely to have some magical feature on Vitest out-of-the-box as noted in #3234 (comment)

Regarding tsx loader failing on Worker constructor, this seems like a well known issue/limitation of current Node loader:

The tsx issue suggests one workaround privatenumber/tsx#354 (comment) and that would probably work.

@sheremet-va
Copy link
Member

The equivalent cannot work just fine in the browser mode because the browser mode doesn't support node:worker_threads module.

If you are testing web worker, the recommendation is to use @vitest/web-worker package.

@sheremet-va sheremet-va closed this as not planned Won't fix, can't repro, duplicate, stale May 22, 2024
@andreashuber69
Copy link
Author

The equivalent cannot work just fine in the browser mode because the browser mode doesn't support node:worker_threads module.

With equivalent code I meant code implemented in terms of web worker, see test-browser script.

@andreashuber69
Copy link
Author

andreashuber69 commented May 22, 2024

For anyone landing here looking for a solution: I didn't get it to work. I tried with the following lines

        const tsx = new URL("../node_modules/tsx/dist/cli.mjs", import.meta.url);
        const worker = new Worker(tsx, { env: SHARE_ENV, argv: ["./src/nodeWorker.ts"] });

Both cli.mjs and nodeWorker.ts are found, but apparently the message posted on the main thread never makes it to the worker.

@hi-ogawa
Copy link
Contributor

hi-ogawa commented May 23, 2024

Oh right, tsx probably spawns extra process in between, so Node's out-of-the-box worker communication doesn't work.

Just reading back the original Node issue nodejs/node#47747 (comment), it might be that the fix is near in Node 22, but I just double checked it still doesn't work (and it actually breaks more for some use cases).

For the workaround on Node 20, I think you can use tsx/esm/api like this to wrap your worker. I just tested this approach here https://github.com/hi-ogawa/reproductions/blob/main/vitest-5757-ts-worker/src/ts-worker/index.mjs

import { Worker } from "node:worker_threads";

class TsWorker extends Worker {
  constructor(filename, options = {}) {
    options.workerData ??= {};
    options.workerData.__ts_worker_filename = filename.toString();
    super(new URL("./worker.mjs", import.meta.url), options);
  }
}

const worker = new TsWorker(new URL("./your-actual-worker.ts", import.meta.url));
// worker.mjs
import { tsImport } from "tsx/esm/api";
import { workerData } from "node:worker_threads";

tsImport(workerData.__ts_worker_filename, import.meta.url);

@andreashuber69
Copy link
Author

andreashuber69 commented May 23, 2024

For the workaround on Node 20, I think you can use tsx/esm/api like this to wrap your worker.

Indeed, that works. I've modified vitest-repro accordingly and adapted the instructions for the reproduction to check out the original code.

Thank you very much!

@daniel-nagy
Copy link

If you don't want the stain of a non-typescript file on your filesystem (or just want to avoid the extra file)

import { type WorkerOptions, Worker } from "node:worker_threads";

const worker = /* JavaScript */ `
  import { createRequire } from "node:module";
  import { workerData } from "node:worker_threads";

  const filename = "${import.meta.url}";
  const require = createRequire(filename);
  const { tsImport } = require("tsx/esm/api");
  
  tsImport(workerData.__ts_worker_filename, filename);
`;

export class TsWorker extends Worker {
  constructor(filename: string | URL, options: WorkerOptions = {}) {
    options.workerData ??= {};
    options.workerData.__ts_worker_filename = filename.toString();
    super(new URL(`data:text/javascript,${worker}`), options);
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants