From b0666a39d7d9a79ac806a24a2f450fc3e1bc99a2 Mon Sep 17 00:00:00 2001 From: Shubh Porwal Date: Thu, 10 Sep 2026 11:17:05 +0530 Subject: [PATCH] Require `BatchedBridge` lazily in `NativeModules` `NativeModules` requires `BatchedBridge` at module scope, which constructs a `MessageQueue` and defines `global.__fbBatchedBridge`. In bridgeless mode the module resolves to `global.nativeModuleProxy` and the bridge is never read again: its only users are `genModule` and `genMethod`, which run from the `__fbBatchedBridgeConfig` branch or via `__fbGenNativeModule` from the legacy executor. Bundles built with `inlineRequires` (the default in `@react-native/metro-config`) already defer this require. Bundles built without it, such as those from `@expo/metro-config`, evaluate `BatchedBridge` and `MessageQueue` at startup with no consumer. Move the require into a cached getter, matching the `RN$Bridgeless` guards in `registerCallableModule` and `JSTimers`. Changelog: [GENERAL] [CHANGED] - Require `BatchedBridge` lazily from `NativeModules` so bridgeless startup no longer instantiates `MessageQueue`. Co-Authored-By: Claude Fable 5.1 --- .../Libraries/BatchedBridge/NativeModules.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-native/Libraries/BatchedBridge/NativeModules.js b/packages/react-native/Libraries/BatchedBridge/NativeModules.js index 3e16553e26d1..2753507129e7 100644 --- a/packages/react-native/Libraries/BatchedBridge/NativeModules.js +++ b/packages/react-native/Libraries/BatchedBridge/NativeModules.js @@ -11,10 +11,19 @@ 'use strict'; import type {ExtendedError} from '../Core/ExtendedError'; +import typeof BatchedBridgeT from './BatchedBridge'; -const BatchedBridge = require('./BatchedBridge').default; const invariant = require('invariant'); +// Lazy-require to avoid loading the legacy bridge in bridgeless mode. +let BatchedBridge: ?BatchedBridgeT = null; +function getBatchedBridge(): BatchedBridgeT { + if (BatchedBridge == null) { + BatchedBridge = require('./BatchedBridge').default; + } + return BatchedBridge; +} + export type ModuleConfig = [ string /* name */, ?{...} /* constants */, @@ -77,7 +86,7 @@ function genModule( } if (__DEV__) { - BatchedBridge.createDebugLookup(moduleID, moduleName, methods); + getBatchedBridge().createDebugLookup(moduleID, moduleName, methods); } return {name: moduleName, module}; @@ -106,7 +115,7 @@ function genMethod(moduleID: number, methodID: number, type: MethodType) { // $FlowFixMe[incompatible-type] const enqueueingFrameError: ExtendedError = new Error(); return new Promise((resolve, reject) => { - BatchedBridge.enqueueNativeCall( + getBatchedBridge().enqueueNativeCall( moduleID, methodID, args, @@ -142,7 +151,7 @@ function genMethod(moduleID: number, methodID: number, type: MethodType) { const callbackCount = hasSuccessCallback + hasErrorCallback; const newArgs = args.slice(0, args.length - callbackCount); if (type === 'sync') { - return BatchedBridge.callNativeSyncHook( + return getBatchedBridge().callNativeSyncHook( moduleID, methodID, newArgs, @@ -150,7 +159,7 @@ function genMethod(moduleID: number, methodID: number, type: MethodType) { onSuccess, ); } else { - BatchedBridge.enqueueNativeCall( + getBatchedBridge().enqueueNativeCall( moduleID, methodID, newArgs,