vite icon indicating copy to clipboard operation
vite copied to clipboard

TypeScript WebWorker scripts imported with `new URL()` get inlined as `data:video/mp2t`

Open kulmajaba opened this issue 2 years ago • 8 comments

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

kulmajaba avatar Jan 26 '23 13:01 kulmajaba

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 avatar Jan 28 '23 03:01 K024

@K024 The query suffix does work, so I suppose there are two separate problems at play:

  1. Documentation: importing WebWorkers with contructors using the URL constructor 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.
  2. The URL constructor transforming is inconsistent: setting the Worker constructor with URL constructor 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).

kulmajaba avatar Jan 28 '23 13:01 kulmajaba

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

roddc avatar Sep 19 '23 02:09 roddc

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?

Mica4DEV avatar Mar 11 '24 08:03 Mica4DEV

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).

rkh avatar Apr 09 '24 15:04 rkh

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

rkh avatar Apr 10 '24 22:04 rkh

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.

rkh avatar Apr 15 '24 10:04 rkh