Type-safe Web Worker composables for Vue 3 — useWorker(), a worker pool, and a reactive useWorkerComputed(), with types inferred from the worker file itself.

A UI that stutters from heavy synchronous computations is a clear sign it's time to move that work off the main thread — vue-worker-kit gives you Vue composables for exactly that, built on the real Web Worker API.
Client-side sorting, filtering, or parsing of a large dataset runs in a real system thread — the UI stays responsive even during a heavy recalculation.
The stale run cancels itself, and a new one starts in the background thread a moment later — you can type faster than the worker computes, and the UI never stutters.
A worker pool spreads resizing, format conversion, or thumbnail generation for hundreds of files across multiple CPU cores, and large binary data moves zero-copy via transferables.
A SharedWorker computes the heavy state once for every tab instead of each tab recalculating it separately — switching tabs feels instant.

useWorker runs heavy synchronous tasks (sorting, parsing, image processing) in a real system thread (Worker). The main thread stays free for rendering and event handling — the UI never freezes, even during complex calculations that would cause noticeable jank on the main thread.

createWorkerPool creates a pool of workers, distributing tasks among them with bounded concurrency. The pool.map method processes arrays of data concurrently while preserving result order — this provides real speedups on multi‑core systems by executing in parallel across multiple CPU cores simultaneously, especially effective for batch image processing or calculations.

useWorkerComputed works like a reactive computed, but all heavy recalculations run inside a worker. When dependencies change (e.g., as you type), any outdated run is automatically cancelled and a new one starts with debouncing. The result is always up‑to‑date, while the main thread stays responsive even under frequent source changes.

Input and output types are inferred automatically from the worker file itself via typeof import. You don’t need to describe them twice — neither in the worker nor on the calling side. This provides full type safety, autocompletion, and compile‑time checking without extra boilerplate between the main thread and the worker.

Pass large binary data (ArrayBuffer, OffscreenCanvas) zero‑copy via transferables. Receive intermediate results via streaming (reportChunk) for progressive rendering. Enable LRU caching for repeated computations and automatic retries with exponential backoff for fault‑tolerant operations. A built‑in DevTools panel shows pool state and tasks.

useSharedWorker connects to a single SharedWorker shared by every open tab of the site — heavy state gets computed once instead of separately in each tab. warmup() starts the worker ahead of time, eliminating cold‑start latency. Idle workers shut themselves down on a timeout, and onScopeDispose cleans up on unmount.
The heavy lifting — sorting a large array — runs on a separate thread instead of blocking the UI, reports progress, and can be aborted via a cancel signal.
import { defineWorkerHandler } from 'vue-worker-kit/worker'
export default defineWorkerHandler(async (data: number[], ctx) => {
for (let i = 0; i < data.length; i++) {
if (ctx.signal.aborted) throw ctx.signal.reason
if (i % 10_000 === 0) ctx.reportProgress(i / data.length)
}
return data.sort((a, b) => a - b)
})useWorker() creates the worker lazily and infers the result type straight from the handler — no generic annotation needed.
import { useWorker } from 'vue-worker-kit'
const { run, isRunning, progress, error, cancel } = useWorker<typeof import('./heavy-sort.worker')>(
() => new Worker(new URL('./heavy-sort.worker.ts', import.meta.url), { type: 'module' }),
)
const sorted = await run(hugeArray, { transfer: [hugeArray.buffer] })
// sorted: number[] — inferred from heavy-sort.worker.ts, no generic annotation needed With streaming: true the worker reports chunks as it goes via ctx.reportChunk() — chunks updates reactively while the final result is still pending.
import { useWorker } from 'vue-worker-kit'
const { run, chunks, isRunning } = useWorker<typeof import('./process.worker')>(
() => new Worker(new URL('./process.worker.ts', import.meta.url), { type: 'module' }),
{ streaming: true }, // required — without it `chunks` is undefined, not a ref
)
const finalResult = await run(largeDataset)
watch(chunks, (newChunks) => {
console.log('Received chunk:', newChunks.at(-1))
})
// Worker-side calls ctx.reportChunk(data) as it processes each batch —
// chunks.value fills in progressively while run() is still pending.