Skip to content

Releases: ammarahm-ed/react-native-workers

v1.0.0-alpha.3

Choose a tag to compare

@ammarahm-ed ammarahm-ed released this 28 Jul 18:11

Native-module isolation, zero-copy binary transfer, and Expo SDK 55–57 build fixes.

📖 Release write-up

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 ArrayBuffer transfer. postMessage(v, [buf]) moves the backing store;
    createTransferableBuffer(n) returns a buffer that's zero-copy on every hop in both
    directions.
  • SharedBuffer passes through postMessage by reference.
  • Structured clone now supports Map, Set, RegExp, Error and BigInt.
  • enableTransferGuard() — opt-in, makes access to a transferred buffer throw.
  • performance.now() in the worker global scope.
  • Worker-bound RuntimeExecutor on 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 fetch inside an iOS worker never worked. RCTNetworking was built without its
    URL-handler provider, so every request failed with "No suitable URL request handler found".
  • RCTImageLoader in a worker had no loaders or decoders, and its peer moduleRegistry
    lookup returned nil — 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 AppContext only 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 RCTTurboModuleManager initializer).
  • 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: Error name/message escaping, typed-array view transfer, and a decode path that
    silently dropped messages.

Known limitations

  • Hermes has no ArrayBuffer detach, so transfer neutering is simulated: the message path
    refuses to reuse a transferred buffer and .detached reports true, but raw reads still
    succeed unless you call enableTransferGuard().
    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-worker AppContext on 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

Pre-release

Choose a tag to compare

@ammarahm-ed ammarahm-ed released this 26 Jul 17:35

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@alpha
import { 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
    real Worker)
  • 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, and BigInt don't structured-clone yet.
  • Transfer-list detach and SharedArrayBuffer are out of scope — Hermes lacks both;
    SharedBuffer is the supported shared-memory path.
  • No synchronous cross-thread calls. Synchronous access is via the shared
    primitives; calls are async.
  • Thread is 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.

Links