From 42c1c4630cdc7a435a580b88b4913ba0c396cc7a Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Tue, 14 Jul 2026 17:11:07 +0100 Subject: [PATCH] [0.87] Bootstrap Metro from InitializeCore until setup-env is graph-reachable Summary: Fresh 0.87 projects redbox on launch with: Failed to call into JavaScript module method RCTDeviceEventEmitter.emit(). Module has not been registered as callable. Registered callable JavaScript modules (n = 1): AppRegistry. Core initialization never runs. Metro's `getModulesRunBeforeMainModule` (see metro/src/lib/getAppendScripts.js) only emits the run-before `__r()` call for a module that is *already present in the bundle graph*, and silently skips it otherwise. #57475 switched the run-before target from `Libraries/Core/InitializeCore` to `src/setup-env.js`. `InitializeCore` is reachable in the graph (imported by `Libraries/ReactPrivate/ReactNativePrivateInitializeCore.js`), but `src/setup-env.js` is imported by nothing, so Metro skips it and core init never runs. The two modules are functionally identical (both call `setUpDefaultReactNativeEnvironment().default()`). Evidence (bundle tail): - setup-env target: `__r(0);` (broken) - InitializeCore target: `__r(110); __r(0);` (fixed) This is an INTERIM stopgap to unblock the 0.87 RC. It only changes the internal Metro bootstrap target back to `InitializeCore`; the public `react-native/setup-env` entry point and its deprecation of InitializeCore are unchanged. The proper fix (make `setup-env` graph-reachable, or make Metro treat `getModulesRunBeforeMainModule` entries as graph roots) should land on main and supersede this. Changelog: [General][Fixed] - Fix apps failing to boot ("RCTDeviceEventEmitter not registered as callable") due to core init not running. Test Plan: - `yarn test-release-local -t RNTestProject -p iOS`: app boots without redbox. - Bundle tail now contains `__r(); __r(0);` instead of just `__r(0);`. --- .../src/utils/loadMetroConfig.js | 13 ++++++------- packages/metro-config/src/index.flow.js | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/community-cli-plugin/src/utils/loadMetroConfig.js b/packages/community-cli-plugin/src/utils/loadMetroConfig.js index a41b028fa29a..43d9e4126af7 100644 --- a/packages/community-cli-plugin/src/utils/loadMetroConfig.js +++ b/packages/community-cli-plugin/src/utils/loadMetroConfig.js @@ -60,17 +60,16 @@ function getCommunityCliDefaultConfig( return { resolver, serializer: { - // We can include multiple copies of setup-env here because Metro will + // We can include multiple copies of InitializeCore here because metro will // only add ones that are already part of the bundle getModulesRunBeforeMainModule: () => [ - // NOTE: ctx.reactNativePath is an absolute path, therefore we need to - // reference setup-env.js here by exact path specifier. - require.resolve(path.join(ctx.reactNativePath, 'src/setup-env.js'), { - paths: [ctx.root], - }), + require.resolve( + path.join(ctx.reactNativePath, 'Libraries/Core/InitializeCore'), + {paths: [ctx.root]}, + ), ...outOfTreePlatforms.map(platform => require.resolve( - `${ctx.platforms[platform].npmPackageName}/setup-env`, + `${ctx.platforms[platform].npmPackageName}/Libraries/Core/InitializeCore`, {paths: [ctx.root]}, ), ), diff --git a/packages/metro-config/src/index.flow.js b/packages/metro-config/src/index.flow.js index dcac214b3978..03a9aed5ace4 100644 --- a/packages/metro-config/src/index.flow.js +++ b/packages/metro-config/src/index.flow.js @@ -61,7 +61,7 @@ export function getDefaultConfig(projectRoot: string): ConfigT { serializer: { // Note: This option is overridden in cli-plugin-metro (getOverrideConfig) getModulesRunBeforeMainModule: () => [ - require.resolve('react-native/setup-env'), + require.resolve('react-native/Libraries/Core/InitializeCore'), ], getPolyfills: () => require('@react-native/js-polyfills')(), isThirdPartyModule({path: modulePath}: Readonly<{path: string, ...}>) {