DOMException when using web worker during dev with backend integration
Describe the bug
I am trying to use web workers in order to not clog the main thread (when computing hashes of large files for instance).
We have a backend so I'm using some kind a backend integration in dev mode. As such, for the assets to load properly we're going the "origin" approach (i.e., forcing server.origin be https://localhost:5173 in the vite config). I won't be able to go the "proxy route" as suggested in the documentation due to network constraints.
Using this, the assets load but when using web workers, I encounter the following error:
Uncaught (in promise) DOMException: Failed to construct 'Worker': Script at 'https://localhost:5173/src/utils/hash.worker.js?type=module&worker_file' cannot be accessed from origin 'https://app-dev.net'.
at new WorkerWrapper (https://localhost:5173/src/utils/hash.worker.js?worker&inline:2:18)
at ttt (https://localhost:5173/src/main.js:144:20)
at https://localhost:5173/src/main.js:148:1
I understand why that's the case, but I have no idea as to what I can do to make it work. I tried adding CSP headers to no avail. I know this can be circumvented by using a Blob URL and a raw import, but the code inside the worker has to be transpiled in my case since I'm doing some import there.
Everything works fine when built, I noticed it uses the "Blob trick" too, on the transpiled source.
Is there a way to mimic this in dev mode?
Reproduction
https://stackblitz.com/edit/vitejs-vite-hwpuck
Steps to reproduce
You won't able to reproduce in stackblitz but you can by downloading the code to run it locally.
Open 2 terminals : one for vite and the other one for the backend.
In the vite terminal, run npm install && npm run dev
In the backend terminal, run node server.js
Access the backend (http://localhost:8000). In the browser console, you'll see the error.
System Info
System:
OS: Windows 10 10.0.22621
CPU: (8) x64 AMD Ryzen 5 2400G with Radeon Vega Graphics
Memory: 11.25 GB / 29.94 GB
Binaries:
Node: 20.1.0 - C:\Program Files\nodejs\node.EXE
Yarn: 3.6.0 - C:\Program Files\nodejs\yarn.CMD
npm: 6.14.17 - C:\Program Files\nodejs\npm.CMD
Browsers:
Chrome: 114.0.5735.199
Edge: Spartan (44.22621.1848.0), Chromium (114.0.1823.55)
Internet Explorer: 11.0.22621.1
Used Package Manager
yarn
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.
This is happening because Workers are always fetched with requestMode="same-origin" (https://github.com/whatwg/html/pull/3656#:~:text=The%20top%2Dlevel%20script%20for%20module%20workers%20is%20always%20fetched%20with%20request%20mode%20%22same%2Dorigin%22%20and%0Acredentials%20mode%20%22same%2Dorigin%22.%20Cross%2Dorigin%20workers%20did%20not%20quite%20work%20due%20to%20service%20workers).
The current workaround is to use this constructor:
import workerUrl from './worker.js?worker&url'
const js = `import ${JSON.stringify(new URL(workerUrl, import.meta.url))}`
const blob = new Blob([js], { type: "application/javascript" })
function WorkaroundWorker(options) {
const objURL = URL.createObjectURL(blob)
const worker = new Worker(objURL, { type: "module", name: options?.name })
worker.addEventListener("error", (e) => {
URL.revokeObjectURL(objURL)
})
return worker;
}
related: https://github.com/whatwg/html/issues/6911
It seems we cannot simply include the code above in Vite. This will change the baseURI (self.location.href) to be null.
Indeed the proposed workaround does work. When I tried the Blob trick I was doing it on the worker code itself, I hadn't considered doing it on an import statement instead. Pretty neat! I can even wrap it with Comlink.
I don't know if this warrants a change in Vite itself then. It all happens because I'm running dev mode for a library loaded by a backend on another URL, which seems to be a marginal use case.
Thanks.
@sapphi-red your workaround works ! thanks
@sapphi-red your workaround works ! thanks
Please, can I get an example how to use above workaround
This is happening because Workers are always fetched with requestMode="same-origin" (whatwg/html#3656).
new Workerhascredentialsoption, but IIUC it is disabled.The current workaround is to use this constructor:
import workerUrl from './worker.js?worker&url' const js = `import ${JSON.stringify(new URL(workerUrl, import.meta.url))}` const blob = new Blob([js], { type: "application/javascript" }) function WorkaroundWorker(options) { const objURL = URL.createObjectURL(blob) const worker = new Worker(objURL, { type: "module", name: options?.name }) worker.addEventListener("error", (e) => { URL.revokeObjectURL(objURL) }) return worker; }
please can we get an small example?
This is happening because Workers are always fetched with requestMode="same-origin" (whatwg/html#3656).
new Workerhascredentialsoption, but IIUC it is disabled.The current workaround is to use this constructor:
import workerUrl from './worker.js?worker&url' const js = `import ${JSON.stringify(new URL(workerUrl, import.meta.url))}` const blob = new Blob([js], { type: "application/javascript" }) function WorkaroundWorker(options) { const objURL = URL.createObjectURL(blob) const worker = new Worker(objURL, { type: "module", name: options?.name }) worker.addEventListener("error", (e) => { URL.revokeObjectURL(objURL) }) return worker; }
Useful, thanks
@shubh46
Please, can I get an example how to use above workaround
Monaco Editor + MySQL worker + Vite example:
// userWorker.ts
import workerUrl from "monaco-sql-languages/esm/languages/mysql/mysql.worker.js?worker&url";
// import the worker you need and don't forget about this tail ^^^^^^^^^
const js = `import ${JSON.stringify(new URL(workerUrl, import.meta.url))}`;
const blob = new Blob([js], { type: "application/javascript" });
function WorkaroundWorker([options](options: {name: string})) {
const objURL = URL.createObjectURL(blob);
const worker = new Worker(objURL, { type: "module", name: options?.name });
worker.addEventListener("error", (e) => {
URL.revokeObjectURL(objURL);
});
return worker;
}
self.MonacoEnvironment = {
getWorker: function () {
return WorkaroundWorker({ name: "mysql.worker" });
// ^^^^^^^ here's where the workaround is used
},
};