@mileszim/useworker-vite
v4.2.0
Published
Vite plugin for @mileszim/useworker — run worker functions composed from imports across files and npm packages, just like passing a pure function.
Maintainers
Readme
@mileszim/useworker-vite
A Vite plugin for @mileszim/useworker that lets you write your
worker function across multiple modules and npm packages and invoke it
exactly like passing a pure function.
Why
useWorker(fn) moves fn into a Web Worker by stringifying it
(fn.toString()). That captures only the source text of that one function — any
variable from its closure or any imported binding is not included, so a
worker function that references other modules throws ReferenceError at
runtime:
import { square } from './math'
const compute = (xs) => xs.map(square) // `square` is undefined inside the worker
useWorker(compute) // ❌ ReferenceError: square is not definedThe import graph that square came from was resolved and discarded by your
bundler — it doesn't exist at runtime. The only place it does exist is at
build time, which is what this plugin hooks into.
How it works
- You put worker code in a
*.worker.{js,ts,jsx,tsx}module and import it normally. - The plugin redirects that import to a generated proxy whose named exports are
tagged refs understood by
useWorker. - Each ref is backed by Vite's native
?worker, so the bundler compiles the worker module's entire import graph (local files and npm packages) into a dedicated module-worker chunk. - A tiny RPC dispatcher is appended to the worker so its exports are callable
over
postMessage.
useWorker detects the tagged ref and routes to this module-worker path instead
of the blob/toString path — so the call site is unchanged.
Install
npm install @mileszim/useworker
npm install -D @mileszim/useworker-viteSetup
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { useworkerVite } from '@mileszim/useworker-vite'
export default defineConfig({
plugins: [react(), useworkerVite()],
})Usage
// transforms.worker.ts
import Papa from 'papaparse' // npm dep — bundled into the worker
import { square } from './math' // local import — bundled into the worker
export const compute = (xs: number[]) => xs.map(square)
export const parseCsv = (text: string) => Papa.parse(text).dataimport { useWorker } from '@mileszim/useworker'
import { compute } from './transforms.worker'
function Example() {
const [computeWorker] = useWorker(compute) // reads like a pure function
const run = async () => {
const result = await computeWorker([1, 2, 3]) // runs off the main thread
console.log(result)
}
// ...
}Because TypeScript resolves ./transforms.worker to the real source, compute
keeps its real type signature — argument and return types are inferred at the
call site. (At runtime it is a tagged ref; the plugin makes that swap safe.)
Options
useworkerVite({
// Which modules are treated as worker modules.
include: /\.worker\.[mc]?[jt]sx?$/, // default
// Modules to exclude even if they match `include`.
exclude: undefined,
})Limitations
- Named exports only. Export worker functions as named exports
(
export const fn = …), notexport default. - Worker modules must be statically importable (a normal
import), so the plugin can find them at build time. - Standard Web Worker constraints still apply: no
window/documentaccess, arguments and results must be structured-cloneable, and a worker function can't return a function.
Testing
The runtime behavior is verified end-to-end in a real browser (jsdom cannot execute workers) across both dev and production builds:
pnpm --filter @mileszim/useworker-vite testPoint CHROME_PATH at a Chrome/Chromium binary if it isn't at the default
macOS location.
