Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
7 tasks done
kulmajaba opened this issue Jan 26, 2023 · 7 comments
Open
7 tasks done
Labels
feat: web workers p3-minor-bug An edge case that only affects very specific usage (priority)

Comments

@kulmajaba
Copy link

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

@sapphi-red sapphi-red added p3-minor-bug An edge case that only affects very specific usage (priority) feat: web workers labels Jan 27, 2023
@K024
Copy link

K024 commented Jan 28, 2023

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.

@kulmajaba
Copy link
Author

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

@roddc
Copy link

roddc commented Sep 19, 2023

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

@Mica4DEV
Copy link

Mica4DEV commented Mar 11, 2024

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?

@rkh
Copy link

rkh commented Apr 9, 2024

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
Copy link

rkh commented Apr 10, 2024

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
Copy link

rkh commented Apr 15, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat: web workers p3-minor-bug An edge case that only affects very specific usage (priority)
Projects
None yet
Development

No branches or pull requests

6 participants