TypeScript WebWorker scripts imported with `new URL()` get inlined as `data:video/mp2t`
Describe the bug
With a vanilla TS project, using WebWorkers with the recommended URL syntax works fine:
const worker = new Worker(new URL('./worker.ts', import.meta.url))
Building the project gives me two separate files, a main file and the worker file.
However, if the worker is created with e.g. a factory pattern and still using the URL syntax, some interesting things happen:
const workerFactory = (workerScript: URL, workerOptions: WorkerOptions) => () => new Worker(workerScript, workerOptions);
const main = () => {
const factory = workerFactory(new URL('./worker.ts', import.meta.url), { type: 'module' });
const worker = factory();
...
};
Now, the worker is inlined as Base64, but the MIME type is wrong and set to data:video/mp2t (because .ts is a valid video file extension too).
The use case for a pattern like this is e.g. creating a Promisified version of the WebWorker interface and making it usable with any worker.
Reproduction
https://stackblitz.com/edit/vitejs-vite-8ewkf4?file=src/main.ts
Steps to reproduce
Run npm run build and check the bundled code.
System Info
System:
OS: Linux 5.0 undefined
CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Memory: 0 Bytes / 0 Bytes
Shell: 1.0 - /bin/jsh
Binaries:
Node: 16.14.2 - /usr/local/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 7.17.0 - /usr/local/bin/npm
npmPackages:
vite: ^4.1.0-beta.1 => 4.1.0-beta.1
Originally encountered locally with Node 16.15.1 and vite: ^4.0.4 => 4.0.4
Used Package Manager
npm
Logs
No response
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guidelines.
- [X] Read the docs.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to vuejs/core instead.
- [X] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- [X] The provided reproduction is a minimal reproducible example of the bug.
Hi, @kulmajaba. When you write your code like
workerFactory(new URL('./worker.ts', import.meta.url), { type: 'module' });
the URL is treated as an asset url, and will be transformed by the asset plugin in an unintended way.
The correct way to get the url of a worker can be found here:
import myWorkerUrl from './worker?worker&url'
If you also like es module for your workers, you should also change this option.
@K024 The query suffix does work, so I suppose there are two separate problems at play:
- Documentation: importing WebWorkers with contructors using the
URLconstructor is the recommended way according to Vite docs, but there should be a mention that this will sometimes cause unintended behavior with more complex patterns. - The
URLconstructor transforming is inconsistent: setting theWorkerconstructor withURLconstructor syntax into a variable bundles the worker separately, while using the constructor inside a function call causes the worker to be inlined (into an incorrect format).
I'm trying to import a worker script in my Electron app(main process).
Why did I get '/test-438b85ef.js' by import MyWorker from './workers/test?worker&url'; ?
The path is wrong and the worker file wasn't emitted in the dist directory.
Here is my vite config:
build: {
ssr: true,
sourcemap: 'inline',
target: `node${node}`,
outDir: 'dist',
assetsDir: '.',
minify: process.env.MODE !== 'development',
lib: {
entry: 'src/index.ts',
formats: ['cjs'],
},
rollupOptions: {
output: {
entryFileNames: '[name].js',
},
},
emptyOutDir: true,
reportCompressedSize: false,
},
I'm trying to import a worker script in my Electron app(main process). Why did I get '/test-438b85ef.js' by
import MyWorker from './workers/test?worker&url';? The path is wrong and the worker file wasn't emitted in the dist directory.
Did you find any solution for this?
Hi, @kulmajaba. When you write your code like
workerFactory(new URL('./worker.ts', import.meta.url), { type: 'module' });the URL is treated as an asset url, and will be transformed by the asset plugin in an unintended way.
The correct way to get the url of a worker can be found here:
import myWorkerUrl from './worker?worker&url'If you also like es module for your workers, you should also change this option.
This solution does not work for using a factory and thus doesn't actually work for SharedWorker with a Worker fallback (which in turn is required for Android support).
As a workaround, You can name the factory Worker or SharedWorker, and Vite will be happy.
So this should work:
const Worker = (workerScript: URL, workerOptions: WorkerOptions) => () => new globalThis.Worker(workerScript, workerOptions);
const main = () => {
const factory = Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
const worker = factory();
...
};
Example of how I'm using this:
const hasSharedWorker = (globalThis.SharedWorker as any) != (globalThis.Worker as any)
const SharedWorker = hasSharedWorker ? globalThis.SharedWorker : Worker
const worker = new SharedWorker(
new URL("./worker.ts", import.meta.url),
{ type: "module", name: "pinto:back", credentials: "include" }
)
const port = hasSharedWorker ? (worker as SharedWorker).port : worker as Worker
Also note that Vite is also not adding a timestamp to the generated URLs in dev mode:
import api from "/vite-dev/repository/api.ts?t=1712992006878";
const worker = new SharedWorker(
new URL('' + "/vite-dev/entrypoints/worker.ts?type=module&worker_file", import.meta.url),
{ type: "module", name: "pinto:back", credentials: "include" }
);
I'm not sure if this is intentional. I can see reasons for and against it.
If you include a timestamp, you might end up with two different shared workers running at the same time.
However, if you don't, you might end up with the old shared worker still running and no new worker starting up. This makes it impossible to write and easily test code that is supposed to handle this exact scenario in production.
This is possibly a new issue.