threadsx

Workers as simple as a function call.

One transparent API over web workers and Node worker threads: spawn() a worker, call its functions, await the results. A maintained, modernized fork of threads.js.

Node 20+ All modern browsers ESM & CommonJS TypeScript MIT
$npm install threadsx

This is the whole idea

Expose functions from a worker, call them from the main thread like any other async function. Same code in the browser and in Node.

master.js
import { spawn, Thread, Worker } from "threadsx"

const auth = await spawn(new Worker("./workers/auth"))
const hashed = await auth.hashPassword("Super secret", "1234")

console.log("Hashed password:", hashed)
await Thread.terminate(auth)
workers/auth.js
import sha256 from "js-sha256"
import { expose } from "threadsx/worker"

expose({
  hashPassword(password, salt) {
    return sha256(password + salt)
  }
})
pool.js — bulk work, bounded concurrency
import { Pool, spawn, Worker } from "threadsx"

const pool = Pool(() => spawn(new Worker("./workers/crunch")), 4)

for (const file of files) {
  pool.queue(crunch => crunch(file))
}
await pool.completed()
await pool.terminate()
stream.js — observables from workers
import { spawn, Worker } from "threadsx"

const counter = await spawn(new Worker("./workers/counter"))

// Worker functions can return observables:
counter.values().subscribe(count => {
  console.log("Progress:", count)
})

What to expect

No message-passing boilerplate, no bundler plugins, no stale types.

Transparent async calls

Worker functions look like local async functions. Errors reject the promise with the real error, not a cryptic event.

Thread pools built in

Pool() spawns workers, queues tasks, limits concurrency, and reports events. Terminate it and every worker goes with it.

Observables & streaming

Return an observable from a worker to stream values. Subscribe on the main thread; unsubscribing cancels the job in the worker.

Shared workers across tabs

spawnShared() gives every tab one worker instance — native SharedWorker where available, a BroadcastChannel fallback elsewhere — with broadcast() events to all tabs.

Zero-copy transfers

Wrap ArrayBuffers in Transfer() to move them between threads instead of copying. Non-cloneable values fail with a clear ThreadCloneError.

Bundler-native

Works out of the box with webpack 5, Vite, esbuild and rollup via new Worker(new URL(…, import.meta.url)). Real ESM and CommonJS builds, no plugin required.

Actively maintained

TypeScript-first with self-contained types, leak-audited worker lifecycle, and a test suite that runs on Linux, macOS, Windows and real Chromium in CI.

worker_threads Web Workers webpack 5 Vite esbuild rollup

Ready to parallelize?

Spin up a worker in one line and await the result.

Get started