threads.js
A uniform spawn()/expose() API for offloading work to worker threads and web workers across Node.js, browsers, and Electron.
Repository Health
Technical Analysis
threads.js gives Node.js, browser, and Electron applications a single API for offloading CPU-intensive work to worker threads. Instead of writing separate code paths for worker_threads, web workers, and the tiny-worker fallback used on Node versions below 12, you write one expose() call in the worker file and one spawn() call in the calling code, and threads.js selects the right underlying implementation for the platform it runs on.
Exposed worker functions behave like ordinary async functions or Observables, so calling into a worker looks like calling any other async function rather than juggling postMessage/onmessage handlers directly. A built-in thread pool handles bulk task queuing and emits lifecycle events (taskQueued, taskStart, taskCompleted, terminated), while structured error propagation, transferable-object support, and pluggable serializers round out how data and failures move between threads.
What You Get
- A uniform spawn()/expose() API that hides the differences between worker_threads, web workers, and the tiny-worker fallback
- A built-in thread Pool() for queuing and running bulk tasks across a fixed set of workers, with a subscribable event stream
- Worker functions that can return plain values, Promises, or Observables, so streaming results back to the caller needs no extra plumbing
- Transferable-object support via Transfer() for zero-copy handoff of ArrayBuffers and similar objects between threads
- A companion threads-plugin webpack plugin that auto-detects and bundles
new Worker("./path")expressions
Common Use Cases
- Offloading CPU-heavy work (image processing, hashing, parsing) off a Node.js server’s or browser tab’s main thread
- Running a bounded worker pool to parallelize batch jobs across available CPU cores
- Streaming incremental results from a long-running worker task back to the caller via Observables
- Sharing one worker module unmodified between a browser front end and its Node.js backend
Under The Hood
Architecture
The codebase splits cleanly along the master/worker boundary: src/master/ holds spawn.ts, pool.ts, thread.ts, and platform-specific implementation.{node,browser}.ts files, while src/worker/ holds the mirrored expose() side, with shared concerns (serialization, symbols, transferable objects, the Observable wrapper) factored into top-level src/ modules. spawn() instantiates a platform-appropriate worker, performs an async handshake by waiting for a WorkerInitMessage over postMessage, then hands the connection to invocation-proxy.ts, which turns the worker’s exposed functions or module methods into proxy functions returning Promises or Observables. pool.ts builds a queuing thread pool on top of spawn(), tracking task lifecycle and re-emitting it as a typed event stream (initialized, taskQueued, taskStart, taskCompleted, terminated) that callers can subscribe to independently of individual task results.
Tech Stack
Written in strict-mode TypeScript (tsconfig.json sets strict: true, target es2015), built to both CommonJS (tsc -p tsconfig.json) and ESM (tsconfig-esm.json) outputs, with a separate Rollup pass (rollup.config.js) producing a UMD browser bundle for the worker entry point. Runtime dependencies are deliberately small: observable-fns supplies the Observable implementation exposed from worker functions, debug provides opt-in namespaced logging (threads:master:*, threads:pool:*), and tiny-worker is an optional dependency used only as the Node <12 fallback. The companion threads-plugin webpack plugin (a separate package, listed as a dev dependency here for its own tests) handles bundling worker entry points for consumers.
Code Quality
The test suite is substantial and multi-layered: test/*.test.ts runs under ava against real worker fixtures in test/workers/, test/*.chromium.mocha.ts runs the same kind of scenarios in an actual Chromium instance via puppet-run, and test-tooling/ runs a full webpack-bundling integration test. pool.test.ts demonstrates deep behavioral coverage — asserting exact event-emission order, not just final results. Linting runs via tslint (posttest script), which is a deprecated tool at this point, and GitHub Actions CI (.github/workflows/ci.yml) builds and tests across Ubuntu, macOS, and Windows. The project’s own history shows no commits since mid-2024, so while the existing code is well-tested and typed, it is not actively maintained.
What Makes It Unique
Most worker-thread wrappers target a single runtime (Node’s worker_threads or the browser’s Worker); threads.js instead unifies three distinct worker mechanisms — including an explicit fallback for pre-worker_threads Node versions — behind one API, and extends that abstraction to bundler tooling via its own webpack plugin that rewrites new Worker() expressions automatically. Letting exposed worker functions return Observables (not just Promises) so callers can stream incremental progress or partial results back from a worker is a genuine ergonomic differentiator over plain Promise-based worker wrappers.
Used by 2 apps in this directory
hoodik
File Storage · Security
Self-hosted, end-to-end encrypted cloud storage with browser-based encryption and S3-compatible storage support
Plausible Analytics
Analytics
Open-source, cookie-free web analytics that respects visitor privacy and replaces Google Analytics.