vitejs / vite

Next generation frontend tooling. It's fast!
http://vite.dev
MIT License
68.76k stars 6.21k 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 1 year 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 1 year 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 10 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 9 months ago

@sapphi-red your workaround works ! thanks

shubh46 commented 6 months ago

@sapphi-red your workaround works ! thanks

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

shubh46 commented 6 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 6 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 3 months 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
    },
};
OskarAsplin-cf commented 3 weeks ago

Thanks @sapphi-red! Made it work with workerpool like this:

import workerpool from 'workerpool';
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" })
const objURL = URL.createObjectURL(blob)

const pool = workerpool.pool(objURL, { type: 'module' });