vitejs / vite

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

Allow importing classic Web Workers (not Module Workers) #2550

Closed ebeloded closed 2 years ago

ebeloded commented 3 years ago

Vite provides an easy way to import Web Workers, as described in the docs:

import MyWorker from './worker?worker'

const worker = new MyWorker()

This gets compiled into module worker initialization:

new Worker("/assets/index.ef0da162.js",{type:"module"})

Module workers are different from classic workers, and their support is very limited (Chrome 80+, no Safari, no Firefox), which means that shipping this code is not an option.

What's more, module workers are not drop-in replacement for classic workers, as they don't support importScripts, which existing implementations rely on to import scripts from CDN:

importScripts('https://unpkg.com/comlink/dist/umd/comlink.js')
importScripts('https://www.gstatic.com/firebasejs/8.3.0/firebase-app.js')

I would like to request support for classic workers by allowing to pass worker options to the worker constructor:

import MyWorker from './worker?worker'

const worker = new MyWorker() // classic worker
const worker = new MyWorker({type: 'classic'}) // classic worker
const worker = new MyWorker({type: 'module'}) // module worker

Worker constructor options should allow all of worker options, which look like this:

interface WorkerOptions {
    credentials?: RequestCredentials;
    name?: string;
    type?: WorkerType;
}

--

The alternative right now is to bundle worker code in a separate process and use the usual worker initialization:

const classicWorker = new Worker('./worker-built-separately.js')

This complicates the process and doesn't provide out of the box file name hashing. To add file name hashing would require further complications.

userquin commented 3 years ago

Is there a way to build with vite the worker with all its dependencies inside (no code splitting)?

I'm testing web workers and I can use { type: 'module'} (via esno + tsup with esm or cjs formats) on Safari and Firefox, the only restriction is to not have imports inside the worker (just plan javascript, es2017 or whatever we want) and building the worker with all dependencies inside requires to configure its dependencies as dev dependencies (in my case Comlink and dexiejs).

For example, I get my logic and put on new project, then I configure scripts directory with: scripts/build.ts

import { execSync } from 'child_process'
// @ts-ignore
import { commands } from './commands'

for (const command of commands)
  execSync(command, { stdio: 'inherit' })

where commands.ts is:

export const commands = [
  'npx tsup src/parse-file-worker.ts --no-splitting --sourcemap --format cjs -d dist/build',
  'npx tsup src/parse-file-worker-module.ts  --sourcemap --target esnext --format esm -d dist/build',
]

then I add a new script to my package.json:

"scripts": {
  "build": "esno scripts/build.ts"
},

My worker looks like this parse-file-worker-module.ts:

import { expose } from 'comlink/dist/esm/comlink' // <== for esm format, for cjs format just use `import { expose } from 'comlink'`
import type { ParseFileFn } from '~/types'
import { parseFileAsync } from '~/parse-file-async' // <== 128 module dependencies

const parseFile: ParseFileFn = async(
  file,
  onFileInfo,
  onControlParsed,
  onProgress,
  onProcessError,
  onFinished,
  canceled,
) => {
  await parseFileAsync(
    file,
    onFileInfo,
    onControlParsed,
    onProgress,
    onProcessError,
    onFinished,
    canceled,
  )
}
// SSR compatibility
typeof self !== 'undefined' && expose(parseFile)

and finally I can use it (just setting the output on public directory) and referencing it via:

const worker = new Worker('/parse-file-worker-module.mjs', { type: 'module' })
const parseFile = wrap<ParseFileFn>(worker)
userquin commented 3 years ago

I forgot to mention that we can use ?worker with code splitting via dynamic imports with this (this only will work on chromiun based browsers, tested on Chrome and Edge):

async function useWebWorker() {
  const MyWorkerModule = await import('./my-worker?worker') // <=== code splitting
  return MyWorkerModule.default() // <=== use `default` function
}

Right now, the docs explains how to use with import but no with dynamic imports (the equivalent to previous code is):

import MyWorker from './my-worker?worker'

function useWebWorker() {
  return new MyWorker()
}

This should be included in the web workers section on vite docs: The default export will be a custom worker constructor is not 100% true, only when import is used, it is a javascript hack (typescript definition), with dynamic imports, we need to use what src/node/plugins/worker.ts module exports.

ebeloded commented 3 years ago

@Shinigami92 this issue doesn't have a PR anymore

vigneshpa commented 2 years ago

I am having this same issue

shellwhale commented 2 years ago

Same

poyoho commented 2 years ago

I think it can now be worked using new Worker(new URL(xxx, import.meta.url), {type: 'classic'}) syntax

patak-dev commented 2 years ago

@ebeloded please open a new issue against latest if this doesn't cover your use case.

@poyoho we can also achieve the same without passing the options parameter, no?

new Worker(new URL(xxx, import.meta.url))
poyoho commented 2 years ago

yes, I add the options to show the classic word. hhh

ebeloded commented 2 years ago

Is this a new but undocumented feature? A PR to have this implemented is still open.

poyoho commented 2 years ago

The document has been changed. Maybe it hasn't been released yet. 2.9 hasn't been released yet. Now you can try 2.9.0-beta nine

ebeloded commented 2 years ago

This is awesome @poyoho! Thanks for clarifying.