Skip to content

Clean up JavaScript globals set by secondary runtimes #12

@V3RON

Description

@V3RON

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 / __rnrCallRuntimeFunctionkeep (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_envremove

  • 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.

__rnrRuntimeFunctionCacheRuntimeNameremove (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

  • Remove _is_it_a_list_env from iOS + Android prelude; update docs / example / metro templates
  • Remove __COMPOSE_CHAT_LIST_ENV__ and fallbacks in ThreadedRuntime.tsx
  • Remove __rnrRuntimeFunctionCacheRuntimeName
  • Replace __THREADED_RUNTIME_EVENT_EMITTER_FALLBACK__ with module-level guard
  • Audit and remove global/globalThis self-assignment if no longer needed
  • Trim unused fields from __THREADED_RUNTIME_ENV__ (useMainNativeModules, isBackgroundRuntime, version) or document them as intentionally native-facing
  • Update README / website docs to prefer getCurrentRuntime() over raw globals
  • Add a short “Runtime globals” section to docs listing supported vs internal globals

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions