Releases: ammarahm-ed/react-native-workers
Release list
v1.0.0-alpha.3
Native-module isolation, zero-copy binary transfer, and Expo SDK 55–57 build fixes.
Added
- Worker-local native module events. A worker module's events are delivered straight into
that worker's runtime instead of being dispatched on the host runtime and copied back out.
A worker's network I/O no longer depends on the RN JS thread being free. - Per-worker native-modules queue (Android). Module method bodies run on the worker's own
native queue rather than inline on its JS thread, so a blocking module can't stall the
worker's event loop. - Worker-local peer module resolution. A module asking the context for another module gets
that worker's instance, not the host's. - Zero-copy
ArrayBuffertransfer.postMessage(v, [buf])moves the backing store;
createTransferableBuffer(n)returns a buffer that's zero-copy on every hop in both
directions. SharedBufferpasses throughpostMessageby reference.- Structured clone now supports
Map,Set,RegExp,ErrorandBigInt. enableTransferGuard()— opt-in, makes access to a transferred buffer throw.performance.now()in the worker global scope.- Worker-bound
RuntimeExecutoron RN 0.87. - New docs page:
Hacks & compatibility seams
— every private RN/Hermes/Expo internal this library depends on, what it costs, and what
upstream change would remove it.
Fixed
- XHR and
fetchinside an iOS worker never worked.RCTNetworkingwas built without its
URL-handler provider, so every request failed with "No suitable URL request handler found". RCTImageLoaderin a worker had no loaders or decoders, and its peermoduleRegistry
lookup returnednil— which also broke peer lookups for any third-party iOS module.- Expo SDK 55, 56 and 57 failed to compile on iOS. The per-worker
AppContextonly ever
built against SDK 54, so those apps couldn't build the library at all. - Expo SDK 55/56/57+ support in worker runtimes (the iOS JSI API moved twice since SDK 54).
- RN 0.87 build failure (removed
RCTTurboModuleManagerinitializer). - Teardown: use-after-free in the native queue's self-destruct path; ordering fixed so a
module's own cleanup work isn't dropped. subscription.remove()no longer leaks native subscriptions on iOS.- Codec:
Errorname/message escaping, typed-array view transfer, and a decode path that
silently dropped messages.
Known limitations
- Hermes has no
ArrayBufferdetach, so transfer neutering is simulated: the message path
refuses to reuse a transferred buffer and.detachedreportstrue, but raw reads still
succeed unless you callenableTransferGuard().
Details - Expo modules on iOS SDK 56+ use the forwarding installer, so those module objects live on
the main runtime. Android builds a real per-workerAppContexton every SDK. - Native-module isolation depends on version-sensitive RN internals.
- Thread-safety of a given native module is still that module's own responsibility.
v1.0.0-alpha.2 — first public alpha
Real threads for React Native. Each worker runs your JavaScript in a separate
Hermes runtime on its own OS thread, behind the Worker API you already know from
the browser — postMessage, onmessage, terminate, structured clone. No new
vocabulary, no directive to remember, no closure-capture rules.
📖 Read the announcement post →
npm install @ammarahmed/react-native-workers@alphaimport { Worker } from '@ammarahmed/react-native-workers';
const worker = new Worker('./workers/parse');
worker.onmessage = (e) => setRows(e.data);
worker.postMessage(payload);What's in it
A real JavaScript environment, not a sandbox. Each worker is its own Metro
bundle graph, so you import npm packages, your own utilities, a database client —
and they're just there. Inside you get timers, microtasks, promises, console, and
fetch where the platform provides it. A worker is a headless React Native: no UI,
same engine, same native modules.
Native modules inside workers. C++ (Cxx) TurboModules work by default; platform
(Java/Obj-C) TurboModules and legacy modules are opt-in per worker with
{ nativeModules: true }. NativeEventEmitter events are delivered on the worker's
own thread. This is the piece no other multithreading library for React Native
offers.
Expo modules inside workers. requireNativeModule(...) works in a worker on
both iOS and Android — functions (sync and async), events, and live properties, all
through Expo's own native paths. Ships with an Expo config plugin; works in any
development build on SDK 54–57.
A spectrum of shared data, not just one primitive:
postMessage |
Structured clone — the spec default |
defineModule / JSModule bridge |
One typed TypeScript contract both sides implement: promise-returning methods, events in both directions, readiness handshakes |
SharedStore |
Subscribable state tree with granular setIn / subscribeIn and lazy reads |
SharedValue |
A single synchronous cell — lock-free for numbers (~17M writes/sec) |
SharedBuffer |
True zero-copy shared memory across runtimes for bulk numeric work |
UIWorker — a worker whose JS runs on the platform main/UI thread, on both iOS
and Android. Its runtime is shared and persistent by default, so it survives
navigation and works as a long-lived main-thread service.
Thread (experimental) — lets a worker's own runtime temporarily execute on a
different OS thread, with nothing serialized: same variables, same objects, same
identity. For work that must happen on a particular thread while sharing the
worker's live state. Off until you call enableMultiThreadingExperimental().
Read the rules
before shipping it.
Debuggable. Every background worker registers as its own DevTools target —
breakpoints, stepping, sources — and its console output is forwarded to the host
tagged [Worker:name].
Dev and release both work. In development Metro serves each worker bundle, so
fast refresh applies to worker code. For release, the bundles are built ahead of
time, Hermes-compiled to bytecode (~150 KB each, versus ~1.4 MB for the full
framework) and loaded by the library's own asset reader — one build step, wired up
for bare RN and Expo alike.
Performance
Measured on the example app's in-app benchmark suite (Pixel emulator / iPhone 16
simulator — treat as relative, not absolute):
| Android | iOS | |
|---|---|---|
| Message round-trip | ~0.09 ms/msg | ~0.013 ms/msg |
8 MB Uint8Array transfer |
~1.8 ms (~5 GB/s) | ~6.9 ms (~1.2 GB/s) |
SharedValue write/read |
~0.06 µs/op (~17M writes/sec, lock-free) | ~0.06 µs/op |
| Typed RPC round-trip | ~0.10 ms/call | ~0.017 ms/call |
Requirements
- React Native 0.81.4+, New Architecture, Hermes
- iOS and Android (web is a compatibility fallback that delegates to the browser's
realWorker) - Expo: SDK 54+ with a development build (not Expo Go)
Tested compatibility. A compatibility matrix builds the library into a fresh app
per version on every run: React Native 0.81–0.86 plus floating latest and next,
and Expo SDK 54–57 plus latest.
Honest alpha limitations
Map,Set,RegExp,Error, andBigIntdon't structured-clone yet.- Transfer-list detach and
SharedArrayBufferare out of scope — Hermes lacks both;
SharedBufferis the supported shared-memory path. - No synchronous cross-thread calls. Synchronous access is via the shared
primitives; calls are async. Threadis experimental, opt-in, and gives thread affinity rather than
parallelism (one thread inside a runtime at a time).- Native install-time diagnostic logging is still always-on. It's useful for alpha
bug reports; it'll be gated behind a debug flag before stable.
This is an alpha: the surface is feature-complete for its scope and exercised on
both platforms, but it hasn't had a wide production shakedown. Please
open an issue — bug
reports at this stage are worth a lot.