vitejs / vite

Next generation frontend tooling. It's fast!
http://vitejs.dev
MIT License
67.32k stars 6.06k forks source link

DOMException when using web worker during dev with backend integration #13680

Open Arzaroth opened 1 year ago

Arzaroth commented 1 year ago

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

stackblitz[bot] commented 1 year ago

Fix this issue in StackBlitz Codeflow Start a new pull request in StackBlitz Codeflow.

sapphi-red commented 10 months ago

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

sapphi-red commented 10 months ago

It seems we cannot simply include the code above in Vite. This will change the baseURI (self.location.href) to be null.

Arzaroth commented 8 months ago

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.

lk77 commented 7 months ago

@sapphi-red your workaround works ! thanks

shubh46 commented 4 months ago

@sapphi-red your workaround works ! thanks

Please, can I get an example how to use above workaround

shubh46 commented 4 months ago

This is happening because Workers are always fetched with requestMode="same-origin" (whatwg/html#3656). new Worker has credentials option, 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?

boomsi commented 4 months ago

This is happening because Workers are always fetched with requestMode="same-origin" (whatwg/html#3656). new Worker has credentials option, 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

zakutnya commented 1 month ago

@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
    },
};