Expose 'react-native/setup-env' to replace InitializeCore (#57461)

Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57461

Replace `'react-native/Libraries/Core/InitializeCore'` with an explicit `'react-native/setup-env'` secondary export.

This was previously a special-case path excluded from our ESLint warnings. It is now formalised.

**Changes**

- Add new `src/setup-env.js` entry point and `package.json` mapping.
- Replace references in `packages/metro-config/` and `packages/community-cli-plugin/`.
- Update `warn-on-deep-imports` ESLint rule.

Changelog:
[General][Added] - Add `'react-native/setup-env'` entry point. This replaces the previous side-effectful `'react-native/Libraries/Core/InitializeCore'`.
[General][Deprecated] - Deprecate `'react-native/Libraries/Core/InitializeCore'`. Use `'react-native/setup-env'` instead.

Reviewed By: rubennorte

Differential Revision: D110890810

fbshipit-source-id: 3afe25efa45548d593a1b09c436e36038350b4c5
This commit is contained in:
Alex Hunt
2026-07-07 10:43:04 -07:00
committed by meta-codesync[bot]
parent 857c3d24c1
commit 554baba159
15 changed files with 113 additions and 50 deletions
+1
View File
@@ -56,6 +56,7 @@ experimental.multi_platform.extensions=.android
munge_underscores=true
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/packages/react-native/index.js'
module.name_mapper='^react-native/setup-env$' -> '<PROJECT_ROOT>/packages/react-native/src/setup-env.js'
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/packages/react-native/\1'
module.name_mapper='^@react-native/dev-middleware$' -> '<PROJECT_ROOT>/packages/dev-middleware'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\|xml\|ktx\|heic\|heif\)$' -> '<PROJECT_ROOT>/packages/react-native/Libraries/Image/RelativeImageStub'
@@ -60,16 +60,17 @@ function getCommunityCliDefaultConfig(
return {
resolver,
serializer: {
// We can include multiple copies of InitializeCore here because metro will
// We can include multiple copies of setup-env here because Metro will
// only add ones that are already part of the bundle
getModulesRunBeforeMainModule: () => [
require.resolve(
path.join(ctx.reactNativePath, 'Libraries/Core/InitializeCore'),
{paths: [ctx.root]},
),
// 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],
}),
...outOfTreePlatforms.map(platform =>
require.resolve(
`${ctx.platforms[platform].npmPackageName}/Libraries/Core/InitializeCore`,
`${ctx.platforms[platform].npmPackageName}/setup-env`,
{paths: [ctx.root]},
),
),
@@ -29,10 +29,10 @@ eslintTester.run('../no-deep-imports', rule, {
"import Foo from 'react-native-foo';",
"import Foo from 'react-native-foo/Foo';",
"import Foo from 'react/native/Foo';",
"import 'react-native/Libraries/Core/InitializeCore';",
"require('react-native/Libraries/Core/InitializeCore');",
"import Foo from 'react-native/src/fb_internal/Foo'",
"require('react-native/src/fb_internal/Foo')",
"import 'react-native/setup-env';",
"require('react-native/setup-env');",
],
invalid: [
{
@@ -125,5 +125,31 @@ eslintTester.run('../no-deep-imports', rule, {
],
output: null,
},
{
code: "import 'react-native/Libraries/Core/InitializeCore';",
errors: [
{
messageId: 'useReplacementSource',
data: {
importPath: 'react-native/Libraries/Core/InitializeCore',
replacementSource: 'react-native/setup-env',
},
},
],
output: "import 'react-native/setup-env';",
},
{
code: "require('react-native/Libraries/Core/InitializeCore');",
errors: [
{
messageId: 'useReplacementSource',
data: {
importPath: 'react-native/Libraries/Core/InitializeCore',
replacementSource: 'react-native/setup-env',
},
},
],
output: "require('react-native/setup-env');",
},
],
});
@@ -21,6 +21,8 @@ module.exports = {
messages: {
deepImport:
"'{{importPath}}' React Native deep imports are deprecated. Please use the top level import instead.",
useReplacementSource:
"'{{importPath}}' is deprecated. Please import '{{replacementSource}}' instead.",
},
schema: [],
fixable: 'code',
@@ -31,12 +33,14 @@ module.exports = {
ImportDeclaration(node) {
if (
!isDeepReactNativeImport(node.source) ||
isInitializeCoreImport(node.source) ||
isSecondaryEntryPoint(node.source) ||
isFbInternalImport(node.source)
) {
return;
}
if (reportReplacementSource(node.source)) {
return;
}
if (isDefaultImport(node)) {
const reactNativeSource = node.source.value.slice(
'react-native/'.length,
@@ -88,13 +92,16 @@ module.exports = {
CallExpression(node) {
if (
!isDeepRequire(node) ||
isInitializeCoreImport(node.arguments[0]) ||
isSecondaryEntryPoint(node.arguments[0]) ||
isFbInternalImport(node.arguments[0])
) {
return;
}
if (reportReplacementSource(node.arguments[0])) {
return;
}
const parent = node.parent;
const importPath = node.arguments[0].value;
@@ -123,6 +130,26 @@ module.exports = {
},
};
function reportReplacementSource(source) {
const reactNativeSource = source.value.slice('react-native/'.length);
const mapping = publicAPIMapping[reactNativeSource];
if (!mapping || !mapping.replacementSource) {
return false;
}
context.report({
node: source,
messageId: 'useReplacementSource',
data: {
importPath: source.value,
replacementSource: mapping.replacementSource,
},
fix(fixer) {
return fixer.replaceText(source, `'${mapping.replacementSource}'`);
},
});
return true;
}
function getStandardReport(source) {
return {
node: source,
@@ -167,20 +194,15 @@ module.exports = {
return parts.length > 1 && parts[0] === 'react-native';
}
function isInitializeCoreImport(source) {
if (source.type !== 'Literal' || typeof source.value !== 'string') {
return false;
}
return source.value === 'react-native/Libraries/Core/InitializeCore';
}
function isSecondaryEntryPoint(source) {
if (source.type !== 'Literal' || typeof source.value !== 'string') {
return false;
}
return source.value === 'react-native/asset-registry';
return (
source.value === 'react-native/asset-registry' ||
source.value === 'react-native/setup-env'
);
}
function isFbInternalImport(source) {
@@ -37,6 +37,13 @@ const publicAPIMapping = {
default: 'experimental_LayoutConformance',
types: ['LayoutConformanceProps'],
},
'Libraries/Core/InitializeCore': {
// `InitializeCore` has no public named export; the deep import must be
// swapped for the `react-native/setup-env` entry point entirely.
default: null,
types: null,
replacementSource: 'react-native/setup-env',
},
'Libraries/Lists/FlatList': {
default: 'FlatList',
types: ['FlatListProps'],
+5
View File
@@ -18,6 +18,11 @@ module.exports = {
platforms: ['android', 'ios', 'native'],
},
moduleNameMapper: {
// `setup-env` is a secondary entry point exposed via the package's
// `exports`, but `./jest/resolver.js` strips `exports` and the generic
// mapper below resolves subpaths as literal directory paths. Alias it
// explicitly so it resolves to its `src/` implementation.
'^react-native/setup-env$': `${path.dirname(require.resolve('react-native'))}/src/setup-env.js`,
'^react-native($|/.*)': `${path.dirname(require.resolve('react-native'))}/$1`,
},
resolver: require.resolve('./jest/resolver.js'),
+1
View File
@@ -133,6 +133,7 @@ mock(
'm#react-native/Libraries/Core/InitializeCore',
'm#./mocks/InitializeCore',
);
mock('m#react-native/setup-env', 'm#./mocks/InitializeCore');
mock('m#react-native/Libraries/Core/NativeExceptionsManager');
mock('m#react-native/Libraries/Image/Image', 'm#./mocks/Image');
mock(
+1 -1
View File
@@ -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/Libraries/Core/InitializeCore'),
require.resolve('react-native/setup-env'),
],
getPolyfills: () => require('@react-native/js-polyfills')(),
isThirdPartyModule({path: modulePath}: Readonly<{path: string, ...}>) {
@@ -82,17 +82,3 @@ test('import from other package', () => {
`"import { foo } from 'react-native-foo';"`,
);
});
test('import react-native/Libraries/Core/InitializeCore', () => {
const code = `
import 'react-native/Libraries/Core/InitializeCore';
require('react-native/Libraries/Core/InitializeCore');
export * from 'react-native/Libraries/Core/InitializeCore';
`;
expect(transform(code, [rnDeepImportsWarningPlugin])).toMatchInlineSnapshot(`
"import 'react-native/Libraries/Core/InitializeCore';
require('react-native/Libraries/Core/InitializeCore');
export * from 'react-native/Libraries/Core/InitializeCore';"
`);
});
@@ -38,10 +38,6 @@ function isDeepReactNativeImport(source) {
return parts.length > 1 && parts[0] === 'react-native';
}
function isInitializeCoreImport(source) {
return source === 'react-native/Libraries/Core/InitializeCore';
}
function withLocation(node, loc) {
if (!node.loc) {
return {...node, loc};
@@ -55,7 +51,7 @@ module.exports = ({types: t}) => ({
ImportDeclaration(path, state) {
const source = path.node.source.value;
if (isDeepReactNativeImport(source) && !isInitializeCoreImport(source)) {
if (isDeepReactNativeImport(source)) {
const loc = path.node.loc;
state.import.push({source, loc});
}
@@ -71,10 +67,7 @@ module.exports = ({types: t}) => ({
) {
const source =
args[0].node.type === 'StringLiteral' ? args[0].node.value : '';
if (
isDeepReactNativeImport(source) &&
!isInitializeCoreImport(source)
) {
if (isDeepReactNativeImport(source)) {
const loc = path.node.loc;
state.require.push({source, loc});
}
@@ -83,11 +76,7 @@ module.exports = ({types: t}) => ({
ExportNamedDeclaration(path, state) {
const source = path.node.source;
if (
source &&
isDeepReactNativeImport(source.value) &&
!isInitializeCoreImport(source)
) {
if (source && isDeepReactNativeImport(source.value)) {
const loc = path.node.loc;
state.export.push({source: source.value, loc});
}
@@ -22,6 +22,7 @@
* 1. Require system.
* 2. Bridged modules.
*
* @deprecated Since 0.87. Use `'react-native/setup-env'` instead.
*/
'use strict';
+1
View File
@@ -52,6 +52,7 @@
"types": null,
"default": "./src/asset-registry.js"
},
"./setup-env": "./src/setup-env.js",
"./jest-preset": "./jest-preset.js",
"./rn-get-polyfills": "./rn-get-polyfills.js",
"./src/fb_internal/*": "./src/fb_internal/*",
+1 -1
View File
@@ -11,7 +11,7 @@
'use strict';
// ----------------------------------------------------------------------------
// Secondary react-native/asset-registry entry point.
// react-native/asset-registry
//
// This is an untyped secondary entry point intended to be referenced from
// Metro's `transformer.assetRegistryPath` config option. This entry point may
+22
View File
@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
'use client';
// ----------------------------------------------------------------------------
// react-native/setup-env
//
// Side-effectful module that sets up the core React Native JavaScript
// environment. This includes global timers (`setTimeout` etc), the global
// `console` object, and hooks for printing stack traces with source maps.
// ----------------------------------------------------------------------------
require('./private/setup/setUpDefaultReactNativeEnvironment').default();
@@ -10,7 +10,8 @@
'use strict';
require('react-native/Libraries/Core/InitializeCore');
require('react-native/setup-env');
const React = require('react');
const ReactNative = require('react-native');