@ammarahmed/react-native-workers

中文文档
1.0.0-alpha.4发布于 2026/09/17MITTypeScriptCommonJS零依赖近 30 天下载 2067

React Native Workers Runtime

原始使用文档
文档目录9 节
  1. Why it's different
  2. Workers vs. Worklets
  3. Install
  4. A tour
  5. Bundle size
  6. What's supported
  7. Example app
  8. Contributing
  9. License

React Native Workers

Real threads for React Native. Web Worker–style API, backed by a separate Hermes runtime on its own thread — with structured-clone messaging, timers, shared memory, and full native-module access.

License: MIT Platforms React Native New Architecture

Documentation · Quick start · API reference · Examples


React Native runs all your JavaScript on a single JS thread, and drives UI updates from it. A long parse, a big image filter, or a tight simulation loop blocks that thread — so it can't dispatch updates and your UI stalls.

This library gives you a real one:

import { Worker } from '@ammarahmed/react-native-workers';

const worker = new Worker({
  inline: `self.onmessage = (e) => self.postMessage({ doubled: e.data * 2 });`,
});

worker.onmessage = (e) => console.log(e.data); // { doubled: 42 }
worker.postMessage(21);

It's a headless React Native: no UI, but the same JavaScript engine, the same timers and Promises, and the same native modules you already use.

Why it's different

A real threadIts own Hermes runtime with its own heap and event loop, not a time-slice of yours.
Native modules workTurboModules, legacy modules and Nitro modules all resolve inside a worker — react-native-mmkv, react-native-blob-util and friends work unmodified.
Shared memory, not just messagesA ladder from postMessage to lock-free shared cells and raw shared ArrayBuffers — pick the cost you want to pay.
Typed two-way RPCCall across the boundary like a local async function, in both directions, with one shared type.
Small bundlesWorker bundles ship ~150 KB instead of ~1.4 MB, because the UI half of React Native never enters the graph.
DebuggableEvery worker is its own DevTools target — breakpoints, stepping, and console.log forwarded to the host with a [Worker:name] tag.

Workers vs. Worklets

These get compared a lot, so briefly — they solve different problems and are not competitors:

  • The unit differs. A worklet is a function plus its captured closure, moved into another runtime. A worker is a file with its own Metro bundle graph, so you import npm packages normally and there are no closure-capture rules to learn.
  • Both give you real JS runtimes. Worklet runtimes have event loops and timers too, and Bundle Mode lets you import libraries into them. That part isn't a difference.
  • The API differs. Workers speak the Web spec — new Worker(), postMessage, onmessage. Worklets are a lower-level imperative primitive for scheduling work onto a runtime, which is more flexible and less familiar.
  • For animations and gestures, use Reanimated / Worklets. Their synchronous UI runtime is purpose-built and battle-tested for driving UI at 60–120fps. A message-passing worker shouldn't be your animation layer.
  • For "get this work off my JS thread", workers fit. Parsing, crypto, image work, database access, network I/O — with per-worker native module instances and events that stay on the worker.

The long version, including where Worklets is ahead and corrections from their team, is in the comparison.

Install

npm install @ammarahmed/react-native-workers

Requires the New Architecture with Hermes (RN 0.81.4+, developed against 0.86). Native code is autolinked; run pod install on iOS.

Then wrap your Metro config — this is what keeps worker bundles small:

const { getDefaultConfig } = require('@react-native/metro-config');
const { withWorkers } = require('@ammarahmed/react-native-workers/metro');

module.exports = withWorkers(getDefaultConfig(__dirname));

Full setup — including the Babel plugin for file-based workers and the one-time Android native-module registration — is in the installation guide.

A tour

Workers from files — auto-bundled, one Metro graph each

Add the Babel plugin to babel.config.js, then reference a file relatively:

const worker = new Worker('./workers/heavy-task');

Each entry is compiled into its own bundle — fetched from Metro in development, loaded as a Hermes bytecode asset in release. See the bundling guide and design-docs/release-bundling.md.

Native modules inside a worker

C++ (Cxx) modules and nested workers work by default. Platform (Java/ObjC) modules are opt-in per worker, because building the per-worker TurboModule manager costs memory:

const worker = new Worker('./workers/task', { nativeModules: true });
  • iOS — a per-worker RCTTurboModuleManager resolves any registered ObjC module through the global registry. No app changes.

  • Android — register your packages once, since there is no global registry:

    reactHost.addReactInstanceEventListener(object : ReactInstanceEventListener {
      override fun onReactContextInitialized(context: ReactContext) {
        if (context is ReactApplicationContext) {
          WorkerTurboModules.initialize(context, PackageList(this@MainApplication).packages)
        }
      }
    })
    

RN's core modules (SourceCode, PlatformConstants, DeviceInfo, …) are added automatically. UI-affine modules (UIManager and friends) are denylisted. Details.

Native events — NativeEventEmitter in a worker
// inside a worker
const emitter = new NativeEventEmitter(someNativeModule);
const sub = emitter.addListener('SomeEvent', (payload) => {
  // fires on the worker thread
});

Events from the worker's own modules — C++, Java or ObjC — are delivered straight to that worker and never touch the app's runtime. Events raised by the host (the app's own DeviceEventEmitter, or a module the host owns) are forwarded to workers that registered a listener. Details.

Shared state — SharedStore, SharedValue, SharedBuffer

SharedStore is a C++-backed store shared by the host and every worker, with synchronized reads/writes and change watchers:

const store = new SharedStore('session');
store.set('user', { id: 1, name: 'Ada' });
const off = store.subscribe('user', (key, value) => { /* any runtime wrote */ });

Unlike postMessage, a stored value is an addressable tree — read and update parts of it, and watch just a subtree:

store.setIn('doc', ['user', 'age'], 31);           // encodes only the new leaf
store.subscribeIn('doc', ['user'], (path, value) => { /* ['age'], 31 */ });

For hot paths there are two leaner primitives:

// A single synchronous cell — lock-free for numbers (~17M writes/sec).
const progress = new SharedValue('anim:progress', 0);
progress.value = 0.42;

// Raw shared memory — same bytes in every runtime, zero host calls per element.
const buf = new SharedBuffer('sim', 50_000 * Float64Array.BYTES_PER_ELEMENT);
const view = new Float64Array(buf.arrayBuffer);

SharedValue is ~2× faster than SharedStore for a single value; SharedBuffer is ~4× faster for bulk numeric work. The full ladder, with benchmarks, is in design-docs/shared-data-primitives.md.

Typed RPC — the JSModule bridge and defineModule

Call across the worker boundary as if it were a local async function — a two-way JS→JS RPC:

// worker (JSModule is a global in every worker)
new JSModule('calc', {
  add(a, b) { return a + b; },
  async fetchUser(id) { return await load(id); },
});
// host — fully typed
const calc = worker.module<Calc>('calc');
await worker.ready('calc');
const sum = await calc.add(2, 3); // 5

The worker can call back into the host, modules emit events, and large params are best passed by SharedStore reference rather than cloned per call.

defineModule folds the bridge and reactive state into one contract both sides implement halves of:

export const calc = defineModule<{
  worker: { add(a: number, b: number): number };
  host:   { getConfig(): Config };
  events: { progress: { percent: number } };
  state:  { status: 'idle' | 'busy' };
}>('calc');

Design and wire protocol: design-docs/jsmodule-bridge.md, design-docs/jsmodule-defineModule.md.

UIWorker — a worker on the UI thread

A non-spec worker whose runtime lives on the platform main thread, so it can call UI-affine native methods with no thread hop. Same API as Worker; supported on both platforms. Use sparingly — work here competes with rendering.

const w = new UIWorker({ inline: `self.onmessage = (e) => { /* UI-thread JS */ };` });
From native code

Workers can be created and driven from C++ on any thread via cpp/api/NativeWorkers.h (a JSON message bridge) — useful for background JS triggered by native events.

Bundle size

Each worker is its own Metro graph, so import … from 'react-native' pulls the whole framework — renderer, components, Animated, virtualized-lists — into every worker that reaches it. Third-party native modules import that barrel themselves, so careful worker code alone cannot avoid it.

withWorkers() resolves react-native to a worker-sized shim inside worker graphs only. Measured on the example app (Hermes bytecode, minified):

workerwithoutwith
react-native-gzip (legacy module)1.39 MB149 KB
react-native-mmkv (Nitro)1.42 MB302 KB
react-native-blob-util (TurboModule)1.50 MB264 KB
a worker importing nothing17.8 KB17.8 KB

Exports a worker cannot use (View, StyleSheet, Animated, hooks…) throw on access with a message naming the export, instead of being silently undefined.

What's supported

Structured clone (objects, arrays, cycles, typed arrays, ArrayBuffer, Date, Map, Set, RegExp, Error, BigInt), self / postMessage / onmessage / close / addEventListener, timers, Promises, structuredClone, error propagation to onerror, terminate, worker isolation, and NativeEventEmitter.

Binary data transfers instead of copying: postMessage(v, [buf]) moves the backing store, and createTransferableBuffer(n) gives you a buffer that's zero-copy on every hop.

Native modules inside a worker are isolated — their events are delivered to that worker rather than being raised on the app's runtime and copied back, their method bodies run off the worker's JS thread (Android), and peer lookups resolve to the worker's own instances. A worker's network I/O doesn't depend on the RN JS thread being free.

Also inside a worker: Expo Modules via requireNativeModule(...) on both platforms, and the experimental Thread API for running a worker's own runtime on another thread.

Limits worth knowing. Hermes has no ArrayBuffer detach, so a transferred buffer isn't neutered by the engine — this library refuses to reuse it and .detached reports true, but raw reads still succeed unless you call enableTransferGuard(). SharedArrayBuffer doesn't exist in Hermes; SharedBuffer is the shared-memory path. Native-module isolation leans on private React Native internals, all of which are listed — with what would let me delete them — in Hacks & compatibility seams.

Example app

The example/ app is a gallery of working screens — search service, image filters, downloads, sensors, parallel parsing, notes — plus a conformance suite covering the API surface. Step-by-step build-ups for each screen are in the tutorials.

Contributing

License

MIT © Ammar Ahmed


版本与发布记录

@ammarahmed/react-native-workers 共发布 5 个版本,首次发布于 2026/07/27;过去 12 个月发布了 5 个版本。

版本发布日期距今
1.0.0-alpha.4latest2026/09/171 个月前
1.0.0-alpha.32026/07/293 个月前
1.0.0-alpha.22026/07/273 个月前
1.0.0-alpha.1alpha2026/07/273 个月前
1.0.0-alpha.02026/07/273 个月前

关于 @ammarahmed/react-native-workers 的常见问题

@ammarahmed/react-native-workers 是什么?

React Native Workers Runtime

如何安装 @ammarahmed/react-native-workers?

在项目目录运行 npm install @ammarahmed/react-native-workers,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=19.1.0)、react-native(>=0.81.4)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 rn-workers-bundle,可以用 npx rn-workers-bundle 直接运行。

@ammarahmed/react-native-workers 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ammarahmed/react-native-workers 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@ammarahmed/react-native-workers 可以商用吗?

@ammarahmed/react-native-workers 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ammarahmed/react-native-workers 的下载量有多少?

根据 npm 官方下载统计,@ammarahmed/react-native-workers 在 2026/10/06 – 2026/11/04 这 30 天内被下载 2,067 次,日均约 69 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ammarahmed/react-native-workers 还在维护吗?

最新版本 1.0.0-alpha.4 发布于 2026/09/17(1 个月前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/07/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。