Summary
Secondary runtimes currently inject a mix of public runtime identity globals, legacy Compose Chat compatibility shims, and internal runtime-function JSI hooks onto global. Several names overlap in purpose, some are never read, and app code is encouraged to poke at raw globals instead of the exported API.
This issue tracks consolidating what we keep, removing dead/legacy globals, and documenting the supported surface.
Current globals
| Global |
Set by |
Read by |
global / globalThis |
iOS JSI, Android prelude |
(nothing in-repo) |
_is_it_a_list_env |
iOS JSI, Android prelude |
docs, example/index.js, README |
__THREADED_RUNTIME_ENV__ |
iOS JSI, Android prelude |
core, metro, docs, example |
__COMPOSE_CHAT_LIST_ENV__ |
iOS JSI, Android prelude |
core fallbacks, example |
__rnrRegisterRuntimeFunction |
C++ (RuntimeFunctionJsi.cpp) |
core TS, native scheduler |
__rnrCallRuntimeFunction |
C++ |
core TS, native scheduler |
__rnrRuntimeFunctionCacheRuntimeName |
C++ |
never read |
__THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__ |
core TS (iOS only) |
core TS guard only |
Ephemeral (not stored on global): __rnrResolveRuntimeFunction, __rnrRejectRuntimeFunction — created inline for promise bridging in RuntimeFunctionScheduler.cpp. No action needed.
Fields on __THREADED_RUNTIME_ENV__ that are set but never read from JS in this repo: isBackgroundRuntime, useMainNativeModules, version.
Keep (required)
__THREADED_RUNTIME_ENV__ — keep, make the single source of truth
This is the core runtime identity object. It must exist before the bundle executes so Metro-generated entry files and app bootstrap code can branch correctly.
Minimum useful fields:
runtimeName — used by getCurrentRuntime(), metro entry dispatch, native logging
kind — used for runtime-specific entry file selection (index.<kind>.ts)
Recommendation: expose these through the public API (getCurrentRuntime(), isMainRuntime()) and treat direct global.__THREADED_RUNTIME_ENV__ access as legacy in docs.
__rnrRegisterRuntimeFunction / __rnrCallRuntimeFunction — keep (internal)
Required for cross-runtime function calls. Native code invokes __rnrCallRuntimeFunction on the target runtime; JS registers loaders via __rnrRegisterRuntimeFunction.
These are implementation details, but they must live on the JSI global today because registration happens from JS and invocation happens from C++ on the target runtime thread.
Recommendation: keep for now; optionally nest under a single namespace object in a future major (e.g. global.__RN_RUNTIMES__) to reduce pollution.
Remove or consolidate
_is_it_a_list_env — remove
- Set to
true on every secondary runtime, not just list runtimes — the name is misleading.
- Redundant with
__THREADED_RUNTIME_ENV__: both are always set together in iOS (ThreadedRuntime.mm) and Android (ThreadedRuntime.kt prelude).
- Current checks like
global.__THREADED_RUNTIME_ENV__ || global._is_it_a_list_env === true can become just global.__THREADED_RUNTIME_ENV__ != null.
Migration: replace with !isMainRuntime() or global.__THREADED_RUNTIME_ENV__ in app bootstrap / docs.
__COMPOSE_CHAT_LIST_ENV__ — remove
- Legacy artifact from Native Compose Chat (
kind: 'background-list').
- Duplicates information already on
__THREADED_RUNTIME_ENV__.
- Still referenced as a fallback in
getCurrentRuntime() and currentRuntimeName() in ThreadedRuntime.tsx — that fallback can go once native always sets __THREADED_RUNTIME_ENV__ (which it already does).
Migration: use getCurrentRuntime() / __THREADED_RUNTIME_ENV__ only.
global / globalThis self-assignment — investigate, likely remove
global.setProperty(runtime, "global", global);
global.setProperty(runtime, "globalThis", global);
Nothing in this repo reads these. They may have been a Hermes / older RN workaround.
Action: verify on current Hermes + RN versions; remove if redundant.
__rnrRuntimeFunctionCacheRuntimeName — remove (dead code)
Written in RuntimeFunctionJsi.cpp, never read anywhere. Safe to delete.
__THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__ — remove from global
Only used as a module-load guard in ThreadedRuntime.tsx. A file-scoped let didInstall = false is sufficient; no need to pollute globalThis.
Unused fields on __THREADED_RUNTIME_ENV__ — trim or document
| Field |
Status |
isBackgroundRuntime |
Set in native, never read from JS — derive from kind !== default if needed, or drop |
useMainNativeModules |
Set in native (always true on iOS), never read from JS — native-only config, does not belong on JS global |
version |
Set to 1, never read — remove unless we plan schema migration |
Proposed end state
// Secondary runtime only — before bundle runs
global.__THREADED_RUNTIME_ENV__ = {
kind: string,
runtimeName: string,
};
// Internal — installed by native / Nitro, not part of public API
global.__rnrRegisterRuntimeFunction(id, loader);
global.__rnrCallRuntimeFunction(functionId, argsJson);
Public API for apps:
import { getCurrentRuntime, isMainRuntime } from '@react-native-runtimes/core';
if (!isMainRuntime()) {
require('./.threaded-runtime/entry');
}
Work items
Breaking change notes
Removing _is_it_a_list_env and __COMPOSE_CHAT_LIST_ENV__ is a minor breaking change for apps that still check those names directly. The exported getCurrentRuntime() API is the supported replacement.
Consider a deprecation release that warns when legacy globals are detected before removal.
Summary
Secondary runtimes currently inject a mix of public runtime identity globals, legacy Compose Chat compatibility shims, and internal runtime-function JSI hooks onto
global. Several names overlap in purpose, some are never read, and app code is encouraged to poke at raw globals instead of the exported API.This issue tracks consolidating what we keep, removing dead/legacy globals, and documenting the supported surface.
Current globals
global/globalThis_is_it_a_list_envexample/index.js, README__THREADED_RUNTIME_ENV____COMPOSE_CHAT_LIST_ENV____rnrRegisterRuntimeFunctionRuntimeFunctionJsi.cpp)__rnrCallRuntimeFunction__rnrRuntimeFunctionCacheRuntimeName__THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__Ephemeral (not stored on
global):__rnrResolveRuntimeFunction,__rnrRejectRuntimeFunction— created inline for promise bridging inRuntimeFunctionScheduler.cpp. No action needed.Fields on
__THREADED_RUNTIME_ENV__that are set but never read from JS in this repo:isBackgroundRuntime,useMainNativeModules,version.Keep (required)
__THREADED_RUNTIME_ENV__— keep, make the single source of truthThis is the core runtime identity object. It must exist before the bundle executes so Metro-generated entry files and app bootstrap code can branch correctly.
Minimum useful fields:
runtimeName— used bygetCurrentRuntime(), metro entry dispatch, native loggingkind— used for runtime-specific entry file selection (index.<kind>.ts)Recommendation: expose these through the public API (
getCurrentRuntime(),isMainRuntime()) and treat directglobal.__THREADED_RUNTIME_ENV__access as legacy in docs.__rnrRegisterRuntimeFunction/__rnrCallRuntimeFunction— keep (internal)Required for cross-runtime function calls. Native code invokes
__rnrCallRuntimeFunctionon the target runtime; JS registers loaders via__rnrRegisterRuntimeFunction.These are implementation details, but they must live on the JSI global today because registration happens from JS and invocation happens from C++ on the target runtime thread.
Recommendation: keep for now; optionally nest under a single namespace object in a future major (e.g.
global.__RN_RUNTIMES__) to reduce pollution.Remove or consolidate
_is_it_a_list_env— removetrueon every secondary runtime, not just list runtimes — the name is misleading.__THREADED_RUNTIME_ENV__: both are always set together in iOS (ThreadedRuntime.mm) and Android (ThreadedRuntime.ktprelude).global.__THREADED_RUNTIME_ENV__ || global._is_it_a_list_env === truecan become justglobal.__THREADED_RUNTIME_ENV__ != null.Migration: replace with
!isMainRuntime()orglobal.__THREADED_RUNTIME_ENV__in app bootstrap / docs.__COMPOSE_CHAT_LIST_ENV__— removekind: 'background-list').__THREADED_RUNTIME_ENV__.getCurrentRuntime()andcurrentRuntimeName()inThreadedRuntime.tsx— that fallback can go once native always sets__THREADED_RUNTIME_ENV__(which it already does).Migration: use
getCurrentRuntime()/__THREADED_RUNTIME_ENV__only.global/globalThisself-assignment — investigate, likely removeNothing in this repo reads these. They may have been a Hermes / older RN workaround.
Action: verify on current Hermes + RN versions; remove if redundant.
__rnrRuntimeFunctionCacheRuntimeName— remove (dead code)Written in
RuntimeFunctionJsi.cpp, never read anywhere. Safe to delete.__THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__— remove from globalOnly used as a module-load guard in
ThreadedRuntime.tsx. A file-scopedlet didInstall = falseis sufficient; no need to polluteglobalThis.Unused fields on
__THREADED_RUNTIME_ENV__— trim or documentisBackgroundRuntimekind !== defaultif needed, or dropuseMainNativeModulestrueon iOS), never read from JS — native-only config, does not belong on JS globalversion1, never read — remove unless we plan schema migrationProposed end state
Public API for apps:
Work items
_is_it_a_list_envfrom iOS + Android prelude; update docs / example / metro templates__COMPOSE_CHAT_LIST_ENV__and fallbacks inThreadedRuntime.tsx__rnrRuntimeFunctionCacheRuntimeName__THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__with module-level guardglobal/globalThisself-assignment if no longer needed__THREADED_RUNTIME_ENV__(useMainNativeModules,isBackgroundRuntime,version) or document them as intentionally native-facinggetCurrentRuntime()over raw globalsBreaking change notes
Removing
_is_it_a_list_envand__COMPOSE_CHAT_LIST_ENV__is a minor breaking change for apps that still check those names directly. The exportedgetCurrentRuntime()API is the supported replacement.Consider a deprecation release that warns when legacy globals are detected before removal.