crossbind
GitHub
GUIDE · CONCEPTS · THREADING AND WORKERS

Threading and workers

Two independent switches get confused constantly. runtime: 'st' | 'mt' is a build-time choice about threads inside the wasm module. useWorker is a runtime choice about which JavaScript thread the module lives on. You can use either, both, or neither.

The two axes

runtime: 'st'runtime: 'mt'
useWorker: falsethe default: wasm on the main thread, smallest setuppthreads via SharedArrayBuffer on the main thread - needs COOP/COEP
useWorker: truewasm in one Web Worker, main thread stays free; required for OPFSwasm in a worker, pthreads spawned from there - COOP/COEP plus Worker support
You wantPick
The quickest path to C++ in the browserst, no worker
Persistent storage in the browserst + useWorker: true
CPU-bound parallelism (image, geo, crypto)mt
Both persistence and parallelismmt + useWorker: true
Cloudflare Workers, Deno Deploy, Vercel Edgest only - neither is supported
React Nativemt when performance matters; no host configuration needed

Turning on multithreading

cppjs.config.js
export default {
general: { name: 'myapp' },
paths: { config: import.meta.url },
target: { runtime: 'mt' },
};

The wasm is then compiled with -pthread. Note that this promotes in one direction only: if any dependency is mt, your project becomes mt too - you cannot downgrade a multithreaded library back to single-threaded.

The COOP/COEP requirement

Multithreaded wasm needs SharedArrayBuffer, which browsers gate behind cross-origin isolation. Your host has to send two response headers:

response headers
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Without them SharedArrayBuffer is undefined and init fails quietly. Check it from the console on the deployed page:

browser console
console.log(crossOriginIsolated); "color:#78829a">// must be true
console.log(typeof SharedArrayBuffer); "color:#78829a">// must be 'function'
HostWhere the headers go
Vite dev / previewinjected by @cpp.js/plugin-vite
Webpack / Rspack dev serverinjected by @cpp.js/plugin-webpack
Vercelthe headers array in vercel.json
Netlify, Cloudflare Pagesa _headers file
nginxadd_header Cross-Origin-Opener-Policy same-origin; and the COEP twin
Express / custom servermiddleware setting both on every response
require-corp blocks third-party assets
Cross-origin images, fonts and scripts stop loading unless they send Cross-Origin-Resource-Policy: cross-origin. Either switch to Cross-Origin-Embedder-Policy: credentialless, or proxy those assets through your own origin.

What changes with useWorker

The module moves into a dedicated Web Worker and the main thread talks to a bridged proxy. It looks the same; it behaves asynchronously.

AspectWithout workerWith worker
m.add(2, 3) returns5Promise<5>
new X(...) returnsthe instancea promise - write await new X(...)
m.FS.writeFile(...) returnsundefineda promise
Synchronous callbacks into JSworkdo not - design them as promise round-trips
OPFS storagethrowsworks, when the browser supports it
Shutting downn/ainit.terminate() kills the worker

Set dts: 'promise' in cppjs.config.js so the generated TypeScript matches. Embind objects such as vectors are proxied automatically; m.toArray() and m.toVector() keep working.

Where it does not apply

  • Edge runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge) have no Worker constructor and no SharedArrayBuffer: st only, in-memory filesystem only.
  • React Native routes pthreads through JSI, so mt needs no headers and no isolation. useWorker is meaningless there.
  • Node.js runs mt without any host configuration.

Pitfalls

  • `mt` works in dev but not in production. The dev plugin injected the headers; your host is not. Check crossOriginIsolated.
  • Mixing `mt` and `st` artifacts in one bundle. Incompatible memory layouts - the loader fails with a streaming-compile error. Rebuild from clean after switching.
  • Assuming `mt` implies `useWorker`. It does not; mt without a worker runs pthreads from the main thread.
  • Sync-style code against a worker runtime. instance.method is not a function usually means construction returned a promise you never awaited.
Type to search every guide page and section.
↑↓ navigate↵ openesc close