From 74ba7dda8f14593e9b3e2eebe6f42ce2ee936922 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 8 Jul 2026 06:28:17 -0700 Subject: [PATCH 1/8] Remove react-native/jest-preset subpath (#57481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/react/react-native/pull/57481 It's been two releases since we separated `react-native/jest-preset` in 0.85 and this has been updated in the template. Clean up by removing internal redirect module, public export, and peer dependency. Changelog: [General][Breaking] - `react-native/jest-preset` is removed — all projects must now migrate to `react-native/jest-preset` (package) Reviewed By: rubennorte Differential Revision: D111035221 fbshipit-source-id: d66fed9218c801201c1e965f8163f048934d4200 --- packages/jest-preset/README.md | 6 ------ packages/react-native/jest-preset.js | 26 -------------------------- packages/react-native/package.json | 6 ------ 3 files changed, 38 deletions(-) delete mode 100644 packages/react-native/jest-preset.js diff --git a/packages/jest-preset/README.md b/packages/jest-preset/README.md index de420553b828..c53da96056d0 100644 --- a/packages/jest-preset/README.md +++ b/packages/jest-preset/README.md @@ -36,9 +36,3 @@ module.exports = { ``` You can further customize your Jest configuration by specifying other options. See [Jest's `jest.config.js` documentation](https://jestjs.io/docs/configuration) to learn more. - -### Migration Note - -This Jest preset used to be part of the core `react-native` package and accessible at `react-native/jest-preset.js`. As long as `@react-native/jest-preset` is installed, `react-native/jest-preset.js` will be aliased to this package and continue to work but is deprecated. - -Follow the installation instructions above to migrate to `@react-native/jest-preset` and change `preset: 'react-native'` to `preset: '@react-native/jest-preset` to migrate. diff --git a/packages/react-native/jest-preset.js b/packages/react-native/jest-preset.js deleted file mode 100644 index 5251fcdbd0d2..000000000000 --- a/packages/react-native/jest-preset.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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. - * - * @noflow - * @format - */ - -'use strict'; - -try { - module.exports = require('@react-native/jest-preset'); -} catch (error) { - if (error.code === 'MODULE_NOT_FOUND') { - throw new Error( - `The React Native Jest preset has moved to a separate package. -To migrate, please install "@react-native/jest-preset" and update your -jest.config.js to reference: - preset: '@react-native/jest-preset'`, - ); - } else { - throw error; - } -} diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 524857859285..1cd80e676b4f 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -52,7 +52,6 @@ "types": null, "default": "./src/asset-registry.js" }, - "./jest-preset": "./jest-preset.js", "./rn-get-polyfills": "./rn-get-polyfills.js", "./src/fb_internal/*": "./src/fb_internal/*", "./package.json": "./package.json" @@ -69,7 +68,6 @@ "gradle/libs.versions.toml", "index.js", "index.js.flow", - "jest-preset.js", "Libraries", "LICENSE", "React-Core.podspec", @@ -136,16 +134,12 @@ "featureflags": "node ./scripts/featureflags/index.js" }, "peerDependencies": { - "@react-native/jest-preset": "0.87.0-rc.0", "@types/react": "^19.1.1", "react": "^19.2.3" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@react-native/jest-preset": { - "optional": true } }, "dependencies": { From 2691f4ddc82a0a234fe5b7fd858ce40f2b8bda27 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 8 Jul 2026 06:28:17 -0700 Subject: [PATCH 2/8] Remove react-native/rn-get-polyfills subpath (#57482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/react/react-native/pull/57482 Another cleanup to our `package.json#exports` map ahead of the Strict API rollout. This has been long-migrated to `react-native/js-polyfills` in Metro configs. https://www.internalfb.com/code/fbsource/[960845236d89]/xplat/js/react-native-github/packages/metro-config/src/index.flow.js?lines=66 Changelog: [General][Breaking] - `react-native/rn-get-polyfills` is removed — please use `react-native/js-polyfills` (package) Reviewed By: rubennorte Differential Revision: D111035710 fbshipit-source-id: d36acd7583f76f78c69a07ba7134ce8145d578f9 --- packages/react-native/package.json | 3 --- packages/react-native/rn-get-polyfills.js | 13 ------------- 2 files changed, 16 deletions(-) delete mode 100644 packages/react-native/rn-get-polyfills.js diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 1cd80e676b4f..49cf31cd2145 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -52,7 +52,6 @@ "types": null, "default": "./src/asset-registry.js" }, - "./rn-get-polyfills": "./rn-get-polyfills.js", "./src/fb_internal/*": "./src/fb_internal/*", "./package.json": "./package.json" }, @@ -88,7 +87,6 @@ "ReactApple", "ReactCommon", "README.md", - "rn-get-polyfills.js", "scripts/replace-rncore-version.js", "scripts/bundle.js", "scripts/cocoapods", @@ -147,7 +145,6 @@ "@react-native/codegen": "0.87.0-rc.0", "@react-native/community-cli-plugin": "0.87.0-rc.0", "@react-native/gradle-plugin": "0.87.0-rc.0", - "@react-native/js-polyfills": "0.87.0-rc.0", "@react-native/normalize-colors": "0.87.0-rc.0", "@react-native/virtualized-lists": "0.87.0-rc.0", "anser": "^1.4.9", diff --git a/packages/react-native/rn-get-polyfills.js b/packages/react-native/rn-get-polyfills.js deleted file mode 100644 index bf0d0428de37..000000000000 --- a/packages/react-native/rn-get-polyfills.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * 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'; - -module.exports = require('@react-native/js-polyfills'); From a103ec22270a77992f0efd6828a2f1ce68c2558e Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 8 Jul 2026 09:33:30 -0700 Subject: [PATCH 3/8] Expose 'react-native/setup-env' to replace InitializeCore (#57475) Summary: Pull Request resolved: https://github.com/react/react-native/pull/57475 NOTE: Resubmission of D110890810. 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: D111022546 fbshipit-source-id: c9081997468b55b02c8a89544c7cb2026d625ad3 --- .flowconfig | 1 + jest.config.js | 5 +++ .../src/utils/loadMetroConfig.js | 13 +++--- .../__tests__/no-deep-imports-test.js | 30 ++++++++++++- .../no-deep-imports.js | 44 ++++++++++++++----- packages/eslint-plugin-react-native/utils.js | 7 +++ packages/jest-preset/jest-preset.js | 5 +++ packages/jest-preset/jest/setup.js | 1 + packages/metro-config/src/index.flow.js | 2 +- .../plugin-warn-on-deep-imports-test.js | 14 ------ .../src/plugin-warn-on-deep-imports.js | 17 ++----- .../Libraries/Core/InitializeCore.js | 1 + packages/react-native/package.json | 1 + packages/react-native/src/asset-registry.js | 2 +- packages/react-native/src/setup-env.js | 22 ++++++++++ .../IntegrationTests/IntegrationTestsApp.js | 3 +- 16 files changed, 118 insertions(+), 50 deletions(-) create mode 100644 packages/react-native/src/setup-env.js diff --git a/.flowconfig b/.flowconfig index 4d01ae0c0e40..915f8b340ad8 100644 --- a/.flowconfig +++ b/.flowconfig @@ -56,6 +56,7 @@ experimental.multi_platform.extensions=.android munge_underscores=true module.name_mapper='^react-native$' -> '/packages/react-native/index.js' +module.name_mapper='^react-native/setup-env$' -> '/packages/react-native/src/setup-env.js' module.name_mapper='^react-native/\(.*\)$' -> '/packages/react-native/\1' module.name_mapper='^@react-native/dev-middleware$' -> '/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\)$' -> '/packages/react-native/Libraries/Image/RelativeImageStub' diff --git a/jest.config.js b/jest.config.js index 240abcbfeae2..aa6c08fb0ad2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -26,6 +26,11 @@ module.exports = { '.*': './jest/preprocessor.js', }, resolver: './packages/jest-preset/jest/resolver.js', + moduleNameMapper: { + // `resolver.js` strips `exports`, so alias this subpath to its `src/` impl. + '^react-native/setup-env$': + '/packages/react-native/src/setup-env.js', + }, setupFiles: ['./packages/jest-preset/jest/local-setup.js'], fakeTimers: { enableGlobally: true, diff --git a/packages/community-cli-plugin/src/utils/loadMetroConfig.js b/packages/community-cli-plugin/src/utils/loadMetroConfig.js index 43d9e4126af7..a41b028fa29a 100644 --- a/packages/community-cli-plugin/src/utils/loadMetroConfig.js +++ b/packages/community-cli-plugin/src/utils/loadMetroConfig.js @@ -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]}, ), ), diff --git a/packages/eslint-plugin-react-native/__tests__/no-deep-imports-test.js b/packages/eslint-plugin-react-native/__tests__/no-deep-imports-test.js index d28b33f45835..bf14e917f7bf 100644 --- a/packages/eslint-plugin-react-native/__tests__/no-deep-imports-test.js +++ b/packages/eslint-plugin-react-native/__tests__/no-deep-imports-test.js @@ -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');", + }, ], }); diff --git a/packages/eslint-plugin-react-native/no-deep-imports.js b/packages/eslint-plugin-react-native/no-deep-imports.js index 6446c70ba6ac..e84a1f24d45b 100644 --- a/packages/eslint-plugin-react-native/no-deep-imports.js +++ b/packages/eslint-plugin-react-native/no-deep-imports.js @@ -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) { diff --git a/packages/eslint-plugin-react-native/utils.js b/packages/eslint-plugin-react-native/utils.js index 98d1e90ca0b0..728cc26d6ab3 100644 --- a/packages/eslint-plugin-react-native/utils.js +++ b/packages/eslint-plugin-react-native/utils.js @@ -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'], diff --git a/packages/jest-preset/jest-preset.js b/packages/jest-preset/jest-preset.js index 5632cc7fe5c2..bfe131904a21 100644 --- a/packages/jest-preset/jest-preset.js +++ b/packages/jest-preset/jest-preset.js @@ -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'), diff --git a/packages/jest-preset/jest/setup.js b/packages/jest-preset/jest/setup.js index 29f86698a129..483f14cad8f3 100644 --- a/packages/jest-preset/jest/setup.js +++ b/packages/jest-preset/jest/setup.js @@ -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( diff --git a/packages/metro-config/src/index.flow.js b/packages/metro-config/src/index.flow.js index 03a9aed5ace4..dcac214b3978 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/Libraries/Core/InitializeCore'), + require.resolve('react-native/setup-env'), ], getPolyfills: () => require('@react-native/js-polyfills')(), isThirdPartyModule({path: modulePath}: Readonly<{path: string, ...}>) { diff --git a/packages/react-native-babel-preset/src/__tests__/plugin-warn-on-deep-imports-test.js b/packages/react-native-babel-preset/src/__tests__/plugin-warn-on-deep-imports-test.js index 820fc9c59eae..66f247d7112a 100644 --- a/packages/react-native-babel-preset/src/__tests__/plugin-warn-on-deep-imports-test.js +++ b/packages/react-native-babel-preset/src/__tests__/plugin-warn-on-deep-imports-test.js @@ -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';" - `); -}); diff --git a/packages/react-native-babel-preset/src/plugin-warn-on-deep-imports.js b/packages/react-native-babel-preset/src/plugin-warn-on-deep-imports.js index 98f317586fa4..5e98ed59ad74 100644 --- a/packages/react-native-babel-preset/src/plugin-warn-on-deep-imports.js +++ b/packages/react-native-babel-preset/src/plugin-warn-on-deep-imports.js @@ -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}); } diff --git a/packages/react-native/Libraries/Core/InitializeCore.js b/packages/react-native/Libraries/Core/InitializeCore.js index c3ba83318376..8846ce4dea50 100644 --- a/packages/react-native/Libraries/Core/InitializeCore.js +++ b/packages/react-native/Libraries/Core/InitializeCore.js @@ -22,6 +22,7 @@ * 1. Require system. * 2. Bridged modules. * + * @deprecated Since 0.87. Use `'react-native/setup-env'` instead. */ 'use strict'; diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 49cf31cd2145..3a9f62180b4c 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -52,6 +52,7 @@ "types": null, "default": "./src/asset-registry.js" }, + "./setup-env": "./src/setup-env.js", "./src/fb_internal/*": "./src/fb_internal/*", "./package.json": "./package.json" }, diff --git a/packages/react-native/src/asset-registry.js b/packages/react-native/src/asset-registry.js index e98f442e7694..6d3a4cdf34b4 100644 --- a/packages/react-native/src/asset-registry.js +++ b/packages/react-native/src/asset-registry.js @@ -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 diff --git a/packages/react-native/src/setup-env.js b/packages/react-native/src/setup-env.js new file mode 100644 index 000000000000..fbe7f9178b31 --- /dev/null +++ b/packages/react-native/src/setup-env.js @@ -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(); diff --git a/packages/rn-tester/IntegrationTests/IntegrationTestsApp.js b/packages/rn-tester/IntegrationTests/IntegrationTestsApp.js index c1f9cf2c760a..1cbc4335348f 100644 --- a/packages/rn-tester/IntegrationTests/IntegrationTestsApp.js +++ b/packages/rn-tester/IntegrationTests/IntegrationTestsApp.js @@ -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'); From 1a94b0ca30d553d5541c650d76c82e99e5f42b17 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 8 Jul 2026 09:33:30 -0700 Subject: [PATCH 4/8] Remove Touchable (base) from public API (#57476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/react/react-native/pull/57476 NOTE: Resubmission of D110488985. **Motivation** The public type for `Touchable` has zero overlap with its runtime value. This is an internal implementation object which is undocumented. https://www.internalfb.com/code/fbsource/[86267213ee84]/xplat/js/react-native-github/packages/react-native/Libraries/Components/Touchable/Touchable.d.ts?lines=18-24 https://www.internalfb.com/code/fbsource/[86267213ee84]/xplat/js/react-native-github/packages/react-native/Libraries/Components/Touchable/Touchable.js?lines=966-986 Replaces D110483989 / https://github.com/react/react-native/pull/57419. **Impact** This is a **breaking change** with few open source consumers. The `onTouchStart` etc prop typings are part of `ViewProps` today, which is a suitable replacement. Across projects I've been testing for the Strict TypeScript API rollout, the only load bearing consumer was Uniwind — where I've sent a PR to address this. - https://github.com/uni-stack/uniwind/pull/592. Changelog: [General][Breaking] - The `Touchable` root export (undocumented) is removed. If you are extending `Touchable` as a type, please use `ViewProps` instead. Reviewed By: rubennorte Differential Revision: D111022549 fbshipit-source-id: bf29ae826d34fb04889b9f56de0f11904fce3f53 --- packages/react-native/ReactNativeApi.d.ts | 51 +------------------ packages/react-native/__typetests__/index.tsx | 17 ------- packages/react-native/index.js | 18 +++++-- packages/react-native/index.js.flow | 2 - 4 files changed, 16 insertions(+), 72 deletions(-) diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index b5cdeaf11758..c4a2d4ff0beb 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<637f1d0b012ebca68c9e020a52eb2803>> + * @generated SignedSource<> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -403,16 +403,6 @@ declare const staggerImpl: ( time: number, animations: Array, ) => CompositeAnimation -declare const States: { - ERROR: "ERROR" - NOT_RESPONDER: "NOT_RESPONDER" - RESPONDER_ACTIVE_LONG_PRESS_IN: "RESPONDER_ACTIVE_LONG_PRESS_IN" - RESPONDER_ACTIVE_LONG_PRESS_OUT: "RESPONDER_ACTIVE_LONG_PRESS_OUT" - RESPONDER_ACTIVE_PRESS_IN: "RESPONDER_ACTIVE_PRESS_IN" - RESPONDER_ACTIVE_PRESS_OUT: "RESPONDER_ACTIVE_PRESS_OUT" - RESPONDER_INACTIVE_PRESS_IN: "RESPONDER_INACTIVE_PRESS_IN" - RESPONDER_INACTIVE_PRESS_OUT: "RESPONDER_INACTIVE_PRESS_OUT" -} declare const subtract: typeof $$AnimatedImplementation.subtract declare const subtractImpl: ( a: AnimatedNode_default | number, @@ -456,7 +446,6 @@ declare const ToastAndroid_default: { yOffset: number, ) => void } -declare const Touchable: typeof TouchableImpl_default declare const Touchable_default: ( props: TouchableOpacityProps & { ref?: React.Ref @@ -468,33 +457,6 @@ declare const TouchableHighlight_default: ( ref?: React.Ref }, ) => React.ReactNode -declare const TouchableImpl_default: { - Mixin: typeof TouchableMixinImpl - renderDebugView: ($$PARAM_0$$: { - color: ColorValue - hitSlop?: EdgeInsetsProp - }) => null | React.ReactNode -} -declare const TouchableMixinImpl: { - withoutDefaultFocusAndBlur: {} - componentDidMount: () => void - componentWillUnmount: () => void - touchableGetInitialState: () => { - touchable: { - responderID: GestureResponderEvent["currentTarget"] | undefined - touchState: TouchableState | undefined - } - } - touchableHandleBlur: (e: BlurEvent) => void - touchableHandleFocus: (e: FocusEvent) => void - touchableHandleResponderGrant: (e: GestureResponderEvent) => void - touchableHandleResponderMove: (e: GestureResponderEvent) => void - touchableHandleResponderRelease: (e: GestureResponderEvent) => void - touchableHandleResponderTerminate: (e: GestureResponderEvent) => void - touchableHandleResponderTerminationRequest: () => any - touchableHandleStartShouldSetResponder: () => any - touchableLongPressCancelsPress: () => boolean -} declare const TouchableOpacity: typeof Touchable_default declare const UIManager: typeof UIManager_default declare const UIManager_default: UIManagerJSInterface @@ -5371,7 +5333,6 @@ declare type TimingAnimationConfig = Readonly< } > declare type ToastAndroid = typeof ToastAndroid -declare type Touchable = typeof Touchable declare type TouchableHighlight = typeof TouchableHighlight declare type TouchableHighlightBaseProps = { readonly activeOpacity?: number @@ -5478,15 +5439,6 @@ declare type TouchableOpacityTVProps = { readonly nextFocusRight?: number readonly nextFocusUp?: number } -declare type TouchableState = - | typeof States.ERROR - | typeof States.NOT_RESPONDER - | typeof States.RESPONDER_ACTIVE_LONG_PRESS_IN - | typeof States.RESPONDER_ACTIVE_LONG_PRESS_OUT - | typeof States.RESPONDER_ACTIVE_PRESS_IN - | typeof States.RESPONDER_ACTIVE_PRESS_OUT - | typeof States.RESPONDER_INACTIVE_PRESS_IN - | typeof States.RESPONDER_INACTIVE_PRESS_OUT declare function TouchableWithoutFeedback( props: TouchableWithoutFeedbackProps, ): React.ReactNode @@ -6174,7 +6126,6 @@ export { TextProps, // 58466ea1 TextStyle, // b62b8399 ToastAndroid, // 88a8969a - Touchable, // c15da0a2 TouchableHighlight, // 20ba0199 TouchableHighlightInstance, // b510c0eb TouchableHighlightProps, // 337a9164 diff --git a/packages/react-native/__typetests__/index.tsx b/packages/react-native/__typetests__/index.tsx index 1df236c3bc71..54ea8b7694ba 100644 --- a/packages/react-native/__typetests__/index.tsx +++ b/packages/react-native/__typetests__/index.tsx @@ -124,7 +124,6 @@ import { // @ts-ignore SectionListData, ToastAndroid, - Touchable, LayoutAnimation, processColor, experimental_LayoutConformance as LayoutConformance, @@ -487,22 +486,6 @@ class Welcome extends React.Component< export default Welcome; -// TouchableTest -function TouchableTest() { - function basicUsage() { - return Touchable.renderDebugView({ - color: 'mediumspringgreen', - hitSlop: {bottom: 5, top: 5}, - }); - } - - function defaultHitSlop() { - return Touchable.renderDebugView({ - color: 'red', - }); - } -} - export class TouchableHighlightTest extends React.Component { buttonRef = React.createRef>(); diff --git a/packages/react-native/index.js b/packages/react-native/index.js index 201ee115755d..0fb660e9aaf7 100644 --- a/packages/react-native/index.js +++ b/packages/react-native/index.js @@ -149,9 +149,6 @@ module.exports = { get TextInput() { return require('./Libraries/Components/TextInput/TextInput').default; }, - get Touchable() { - return require('./Libraries/Components/Touchable/Touchable').default; - }, get TouchableHighlight() { return require('./Libraries/Components/Touchable/TouchableHighlight') .default; @@ -412,6 +409,21 @@ module.exports = { // #endregion } as ReactNativePublicAPI; +// `Touchable` has been removed from the public API types, but remains +// re-exported at runtime here because of a hanging `react-native-svg` call +// site (fbsource). +// TODO(huntie): Remove this re-export once `react-native-svg` is updated. +/* $FlowFixMe[prop-missing] This is intentional: `Touchable` is a value-only + * re-export that is absent from the public API types. */ +/* $FlowFixMe[invalid-export] This is intentional: `Touchable` is a value-only + * re-export that is absent from the public API types. */ +Object.defineProperty(module.exports, 'Touchable', { + configurable: true, + get() { + return require('./Libraries/Components/Touchable/Touchable').default; + }, +}); + if (__DEV__) { /* $FlowFixMe[prop-missing] This is intentional: Flow will error when * attempting to access InteractionManager. */ diff --git a/packages/react-native/index.js.flow b/packages/react-native/index.js.flow index 9869ed8c5907..51e4a54e0552 100644 --- a/packages/react-native/index.js.flow +++ b/packages/react-native/index.js.flow @@ -175,8 +175,6 @@ export type { } from './Libraries/Components/TextInput/TextInput'; export {default as TextInput} from './Libraries/Components/TextInput/TextInput'; -export {default as Touchable} from './Libraries/Components/Touchable/Touchable'; - export type { TouchableHighlightInstance, TouchableHighlightProps, From 06b8b07cd3d9fc9e91a6906b79ad6c817849240e Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 8 Jul 2026 09:33:30 -0700 Subject: [PATCH 5/8] Remove src/* exports mapping (#57484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/react/react-native/pull/57484 Follows up https://github.com/react/react-native/pull/57277, going further to remove this entry point mapping entirely — D110911864 further supports that these import paths were never used externally (and in the first case, `src/private/` has always been an explicit name). **One caveat** `rn-tester` includes some runtime and Fantom test references to `'react-native/src/private/...'`. - This happens to continue working because we've not enabled `"exports"` resolution under Flow. - These can be classified as repo-local — motivation is simplifying/tightening the root `package.json` for open source. Changelog: [Internal] - (Covered by https://github.com/react/react-native/pull/57277) Reviewed By: rubennorte Differential Revision: D111034634 fbshipit-source-id: 4c80223dcf634201df9c77f8bde96dfb0c6f64f9 --- packages/react-native/package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 3a9f62180b4c..08a8ded99527 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -44,10 +44,6 @@ "default": "./Libraries/*.js" }, "./scripts/*": "./scripts/*", - "./src/*": { - "types": null, - "default": "./src/*.js" - }, "./asset-registry": { "types": null, "default": "./src/asset-registry.js" From c9bf6a75304e48d47e0cd552a3e72845fb1b2474 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Fri, 10 Jul 2026 06:00:50 -0700 Subject: [PATCH 6/8] Expose react-private-interface subpath (#57495) Summary: Pull Request resolved: https://github.com/react/react-native/pull/57495 See [**RFC0894: Removing deep imports from react-native**](https://github.com/react-native-community/discussions-and-proposals/pull/894) Exposes a `'react-native/react-private-interface'` subpath export on the main package, to replace `Libraries/ReactPrivate/ReactNativePrivateInterface.js`. **Impact: Internal** This is a private contract between `react` and `react-native`. - `ReactNativePrivateInterface` is deprecated and we'll migrate React call sites in a future version before cleanup. **Naming** Translate directly to `react-private-interface` ("private interface for React"). There's alternatives here but the explicitness and continuity is helpful, I believe. Changelog: [Internal] Reviewed By: robhogan Differential Revision: D111231527 fbshipit-source-id: 9caa17d2762fe290f21f80894e018e29f95fba46 --- .flowconfig | 1 + .../ReactNativePrivateInterface.js | 121 +-------------- .../ReactNativePrivateInterface.js.flow | 36 +---- packages/react-native/package.json | 4 + .../src/react-private-interface.js | 145 ++++++++++++++++++ .../src/react-private-interface.js.flow | 48 ++++++ 6 files changed, 208 insertions(+), 147 deletions(-) create mode 100644 packages/react-native/src/react-private-interface.js create mode 100644 packages/react-native/src/react-private-interface.js.flow diff --git a/.flowconfig b/.flowconfig index 915f8b340ad8..832667b526f1 100644 --- a/.flowconfig +++ b/.flowconfig @@ -56,6 +56,7 @@ experimental.multi_platform.extensions=.android munge_underscores=true module.name_mapper='^react-native$' -> '/packages/react-native/index.js' +module.name_mapper='^react-native/react-private-interface$' -> '/packages/react-native/src/react-private-interface.js' module.name_mapper='^react-native/setup-env$' -> '/packages/react-native/src/setup-env.js' module.name_mapper='^react-native/\(.*\)$' -> '/packages/react-native/\1' module.name_mapper='^@react-native/dev-middleware$' -> '/packages/dev-middleware' diff --git a/packages/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js b/packages/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js index 190d559a7117..2cf1eca02e95 100644 --- a/packages/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js +++ b/packages/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js @@ -8,124 +8,13 @@ * @format */ -import typeof dispatchNativeEvent from '../../src/private/renderer/events/dispatchNativeEvent'; -import typeof CustomEvent from '../../src/private/webapis/dom/events/CustomEvent'; -import typeof BatchedBridge from '../BatchedBridge/BatchedBridge'; -import typeof legacySendAccessibilityEvent from '../Components/AccessibilityInfo/legacySendAccessibilityEvent'; -import typeof TextInputState from '../Components/TextInput/TextInputState'; -import typeof ExceptionsManager from '../Core/ExceptionsManager'; -import typeof RawEventEmitter from '../Core/RawEventEmitter'; -import typeof ReactFiberErrorDialog from '../Core/ReactFiberErrorDialog'; -import typeof RCTEventEmitter from '../EventEmitter/RCTEventEmitter'; -import typeof { - createPublicInstance, - createPublicRootInstance, - createPublicTextInstance, - getInternalInstanceHandleFromPublicInstance, - getNativeTagFromPublicInstance, - getNodeFromPublicInstance, -} from '../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; -import typeof { - create as createAttributePayload, - diff as diffAttributePayloads, -} from '../ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload'; -import typeof UIManager from '../ReactNative/UIManager'; -import typeof * as ReactNativeViewConfigRegistry from '../Renderer/shims/ReactNativeViewConfigRegistry'; -import typeof flattenStyle from '../StyleSheet/flattenStyle'; -import type {DangerouslyImpreciseStyleProp} from '../StyleSheet/StyleSheet'; -import typeof deepFreezeAndThrowOnMutationInDev from '../Utilities/deepFreezeAndThrowOnMutationInDev'; -import typeof deepDiffer from '../Utilities/differ/deepDiffer'; -import typeof Platform from '../Utilities/Platform'; +import typeof {createPublicTextInstance} from '../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; export type {PublicRootInstance} from '../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; export type PublicTextInstance = ReturnType; -// flowlint unsafe-getters-setters:off +/** + * @deprecated Since 0.88. Use 'react-native/react-private-interface' instead. + */ // eslint-disable-next-line @react-native/monorepo/no-commonjs-exports -module.exports = { - get BatchedBridge(): BatchedBridge { - return require('../BatchedBridge/BatchedBridge').default; - }, - get ExceptionsManager(): ExceptionsManager { - return require('../Core/ExceptionsManager').default; - }, - get Platform(): Platform { - return require('../Utilities/Platform').default; - }, - get RCTEventEmitter(): RCTEventEmitter { - return require('../EventEmitter/RCTEventEmitter').default; - }, - get ReactNativeViewConfigRegistry(): ReactNativeViewConfigRegistry { - return require('../Renderer/shims/ReactNativeViewConfigRegistry'); - }, - get TextInputState(): TextInputState { - return require('../Components/TextInput/TextInputState').default; - }, - get UIManager(): UIManager { - return require('../ReactNative/UIManager').default; - }, - // TODO: Remove when React has migrated to `createAttributePayload` and `diffAttributePayloads` - get deepDiffer(): deepDiffer { - return require('../Utilities/differ/deepDiffer').default; - }, - get deepFreezeAndThrowOnMutationInDev(): deepFreezeAndThrowOnMutationInDev< - {...} | Array, - > { - return require('../Utilities/deepFreezeAndThrowOnMutationInDev').default; - }, - // TODO: Remove when React has migrated to `createAttributePayload` and `diffAttributePayloads` - get flattenStyle(): flattenStyle { - // $FlowFixMe[underconstrained-implicit-instantiation] - // $FlowFixMe[incompatible-type] - return require('../StyleSheet/flattenStyle').default; - }, - get ReactFiberErrorDialog(): ReactFiberErrorDialog { - return require('../Core/ReactFiberErrorDialog').default; - }, - get legacySendAccessibilityEvent(): legacySendAccessibilityEvent { - return require('../Components/AccessibilityInfo/legacySendAccessibilityEvent') - .default; - }, - get RawEventEmitter(): RawEventEmitter { - return require('../Core/RawEventEmitter').default; - }, - get CustomEvent(): CustomEvent { - return require('../../src/private/webapis/dom/events/CustomEvent').default; - }, - get createAttributePayload(): createAttributePayload { - return require('../ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload') - .create; - }, - get diffAttributePayloads(): diffAttributePayloads { - return require('../ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload') - .diff; - }, - get createPublicRootInstance(): createPublicRootInstance { - return require('../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') - .createPublicRootInstance; - }, - get createPublicInstance(): createPublicInstance { - return require('../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') - .createPublicInstance; - }, - get createPublicTextInstance(): createPublicTextInstance { - return require('../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') - .createPublicTextInstance; - }, - get getNativeTagFromPublicInstance(): getNativeTagFromPublicInstance { - return require('../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') - .getNativeTagFromPublicInstance; - }, - get getNodeFromPublicInstance(): getNodeFromPublicInstance { - return require('../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') - .getNodeFromPublicInstance; - }, - get getInternalInstanceHandleFromPublicInstance(): getInternalInstanceHandleFromPublicInstance { - return require('../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') - .getInternalInstanceHandleFromPublicInstance; - }, - get dispatchNativeEvent(): dispatchNativeEvent { - return require('../../src/private/renderer/events/dispatchNativeEvent') - .default; - }, -}; +module.exports = require('../../src/react-private-interface'); diff --git a/packages/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js.flow b/packages/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js.flow index da6972e835df..c9b1e0e77c8b 100644 --- a/packages/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js.flow +++ b/packages/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js.flow @@ -8,34 +8,8 @@ * @format */ -import typeof {createPublicTextInstance as createPublicTextInstanceT} from '../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; - -export type {PublicRootInstance} from '../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; -export type PublicTextInstance = ReturnType; - -export {default as BatchedBridge} from '../BatchedBridge/BatchedBridge'; -export {default as ExceptionsManager} from '../Core/ExceptionsManager'; -export {default as Platform} from '../Utilities/Platform'; -export {default as RCTEventEmitter} from '../EventEmitter/RCTEventEmitter'; -export * as ReactNativeViewConfigRegistry from '../Renderer/shims/ReactNativeViewConfigRegistry'; -export {default as TextInputState} from '../Components/TextInput/TextInputState'; -export {default as UIManager} from '../ReactNative/UIManager'; -export {default as deepDiffer} from '../Utilities/differ/deepDiffer'; -export {default as deepFreezeAndThrowOnMutationInDev} from '../Utilities/deepFreezeAndThrowOnMutationInDev'; -export {default as flattenStyle} from '../StyleSheet/flattenStyle'; -export {default as ReactFiberErrorDialog} from '../Core/ReactFiberErrorDialog'; -export {default as legacySendAccessibilityEvent} from '../Components/AccessibilityInfo/legacySendAccessibilityEvent'; -export {default as RawEventEmitter} from '../Core/RawEventEmitter'; -export {default as CustomEvent} from '../../src/private/webapis/dom/events/CustomEvent'; -export { - create as createAttributePayload, - diff as diffAttributePayloads, -} from '../ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload'; -export { - createPublicRootInstance, - createPublicInstance, - createPublicTextInstance, - getNativeTagFromPublicInstance, - getNodeFromPublicInstance, - getInternalInstanceHandleFromPublicInstance, -} from '../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; +/** + * @deprecated Since 0.88. Use 'react-native/react-private-interface' instead. + */ +export type * from '../../src/react-private-interface'; +export * from '../../src/react-private-interface'; diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 08a8ded99527..79f333b2af9c 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -48,6 +48,10 @@ "types": null, "default": "./src/asset-registry.js" }, + "./react-private-interface": { + "types": null, + "default": "./src/react-private-interface.js" + }, "./setup-env": "./src/setup-env.js", "./src/fb_internal/*": "./src/fb_internal/*", "./package.json": "./package.json" diff --git a/packages/react-native/src/react-private-interface.js b/packages/react-native/src/react-private-interface.js new file mode 100644 index 000000000000..6e965490ebc9 --- /dev/null +++ b/packages/react-native/src/react-private-interface.js @@ -0,0 +1,145 @@ +/** + * 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'; + +// ---------------------------------------------------------------------------- +// react-native/react-private-interface +// +// This is a private entry point allowing React to require React Native +// internals (previously, Libaries/ReactNativePrivateInterface.js). +// +// These APIs should ONLY be used by first party React internals and are not +// part of our public API. +// +// IMPORTANT: Keep this file in sync with react-private-interface.js.flow. +// ---------------------------------------------------------------------------- + +import typeof BatchedBridge from '../Libraries/BatchedBridge/BatchedBridge'; +import typeof legacySendAccessibilityEvent from '../Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent'; +import typeof TextInputState from '../Libraries/Components/TextInput/TextInputState'; +import typeof ExceptionsManager from '../Libraries/Core/ExceptionsManager'; +import typeof RawEventEmitter from '../Libraries/Core/RawEventEmitter'; +import typeof ReactFiberErrorDialog from '../Libraries/Core/ReactFiberErrorDialog'; +import typeof RCTEventEmitter from '../Libraries/EventEmitter/RCTEventEmitter'; +import typeof { + createPublicInstance, + createPublicRootInstance, + createPublicTextInstance, + getInternalInstanceHandleFromPublicInstance, + getNativeTagFromPublicInstance, + getNodeFromPublicInstance, +} from '../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; +import typeof { + create as createAttributePayload, + diff as diffAttributePayloads, +} from '../Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload'; +import typeof UIManager from '../Libraries/ReactNative/UIManager'; +import typeof * as ReactNativeViewConfigRegistry from '../Libraries/Renderer/shims/ReactNativeViewConfigRegistry'; +import typeof flattenStyle from '../Libraries/StyleSheet/flattenStyle'; +import type {DangerouslyImpreciseStyleProp} from '../Libraries/StyleSheet/StyleSheet'; +import typeof deepFreezeAndThrowOnMutationInDev from '../Libraries/Utilities/deepFreezeAndThrowOnMutationInDev'; +import typeof deepDiffer from '../Libraries/Utilities/differ/deepDiffer'; +import typeof Platform from '../Libraries/Utilities/Platform'; +import typeof dispatchNativeEvent from './private/renderer/events/dispatchNativeEvent'; +import typeof CustomEvent from './private/webapis/dom/events/CustomEvent'; + +export type {PublicRootInstance} from '../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; +export type PublicTextInstance = ReturnType; + +// flowlint unsafe-getters-setters:off +// eslint-disable-next-line @react-native/monorepo/no-commonjs-exports +module.exports = { + get BatchedBridge(): BatchedBridge { + return require('../Libraries/BatchedBridge/BatchedBridge').default; + }, + get ExceptionsManager(): ExceptionsManager { + return require('../Libraries/Core/ExceptionsManager').default; + }, + get Platform(): Platform { + return require('../Libraries/Utilities/Platform').default; + }, + get RCTEventEmitter(): RCTEventEmitter { + return require('../Libraries/EventEmitter/RCTEventEmitter').default; + }, + get ReactNativeViewConfigRegistry(): ReactNativeViewConfigRegistry { + return require('../Libraries/Renderer/shims/ReactNativeViewConfigRegistry'); + }, + get TextInputState(): TextInputState { + return require('../Libraries/Components/TextInput/TextInputState').default; + }, + get UIManager(): UIManager { + return require('../Libraries/ReactNative/UIManager').default; + }, + // TODO: Remove when React has migrated to `createAttributePayload` and `diffAttributePayloads` + get deepDiffer(): deepDiffer { + return require('../Libraries/Utilities/differ/deepDiffer').default; + }, + get deepFreezeAndThrowOnMutationInDev(): deepFreezeAndThrowOnMutationInDev< + {...} | Array, + > { + return require('../Libraries/Utilities/deepFreezeAndThrowOnMutationInDev') + .default; + }, + // TODO: Remove when React has migrated to `createAttributePayload` and `diffAttributePayloads` + get flattenStyle(): flattenStyle { + // $FlowFixMe[underconstrained-implicit-instantiation] + // $FlowFixMe[incompatible-type] + return require('../Libraries/StyleSheet/flattenStyle').default; + }, + get ReactFiberErrorDialog(): ReactFiberErrorDialog { + return require('../Libraries/Core/ReactFiberErrorDialog').default; + }, + get legacySendAccessibilityEvent(): legacySendAccessibilityEvent { + return require('../Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent') + .default; + }, + get RawEventEmitter(): RawEventEmitter { + return require('../Libraries/Core/RawEventEmitter').default; + }, + get CustomEvent(): CustomEvent { + return require('./private/webapis/dom/events/CustomEvent').default; + }, + get createAttributePayload(): createAttributePayload { + return require('../Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload') + .create; + }, + get diffAttributePayloads(): diffAttributePayloads { + return require('../Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload') + .diff; + }, + get createPublicRootInstance(): createPublicRootInstance { + return require('../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') + .createPublicRootInstance; + }, + get createPublicInstance(): createPublicInstance { + return require('../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') + .createPublicInstance; + }, + get createPublicTextInstance(): createPublicTextInstance { + return require('../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') + .createPublicTextInstance; + }, + get getNativeTagFromPublicInstance(): getNativeTagFromPublicInstance { + return require('../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') + .getNativeTagFromPublicInstance; + }, + get getNodeFromPublicInstance(): getNodeFromPublicInstance { + return require('../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') + .getNodeFromPublicInstance; + }, + get getInternalInstanceHandleFromPublicInstance(): getInternalInstanceHandleFromPublicInstance { + return require('../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance') + .getInternalInstanceHandleFromPublicInstance; + }, + get dispatchNativeEvent(): dispatchNativeEvent { + return require('./private/renderer/events/dispatchNativeEvent').default; + }, +}; diff --git a/packages/react-native/src/react-private-interface.js.flow b/packages/react-native/src/react-private-interface.js.flow new file mode 100644 index 000000000000..b30331d119f8 --- /dev/null +++ b/packages/react-native/src/react-private-interface.js.flow @@ -0,0 +1,48 @@ +/** + * 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 + */ + +// ---------------------------------------------------------------------------- +// Types entry point for react-native/react-private-interface +// +// IMPORTANT: Keep this file in sync with react-private-interface.js. +// ---------------------------------------------------------------------------- + +import typeof {createPublicTextInstance as createPublicTextInstanceT} from '../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; + +export type {PublicRootInstance} from '../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; +export type PublicTextInstance = ReturnType; + +export {default as BatchedBridge} from '../Libraries/BatchedBridge/BatchedBridge'; +export {default as ExceptionsManager} from '../Libraries/Core/ExceptionsManager'; +export {default as Platform} from '../Libraries/Utilities/Platform'; +export {default as RCTEventEmitter} from '../Libraries/EventEmitter/RCTEventEmitter'; +export * as ReactNativeViewConfigRegistry from '../Libraries/Renderer/shims/ReactNativeViewConfigRegistry'; +export {default as TextInputState} from '../Libraries/Components/TextInput/TextInputState'; +export {default as UIManager} from '../Libraries/ReactNative/UIManager'; +export {default as deepDiffer} from '../Libraries/Utilities/differ/deepDiffer'; +export {default as deepFreezeAndThrowOnMutationInDev} from '../Libraries/Utilities/deepFreezeAndThrowOnMutationInDev'; +export {default as flattenStyle} from '../Libraries/StyleSheet/flattenStyle'; +export {default as ReactFiberErrorDialog} from '../Libraries/Core/ReactFiberErrorDialog'; +export {default as legacySendAccessibilityEvent} from '../Libraries/Components/AccessibilityInfo/legacySendAccessibilityEvent'; +export {default as RawEventEmitter} from '../Libraries/Core/RawEventEmitter'; +export {default as CustomEvent} from './private/webapis/dom/events/CustomEvent'; +export { + create as createAttributePayload, + diff as diffAttributePayloads, +} from '../Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload'; +export { + createPublicRootInstance, + createPublicInstance, + createPublicTextInstance, + getNativeTagFromPublicInstance, + getNodeFromPublicInstance, + getInternalInstanceHandleFromPublicInstance, +} from '../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance'; +export {default as dispatchNativeEvent} from './private/renderer/events/dispatchNativeEvent'; From ee26e3de12002d286d335dc4b67e6b2571d3a368 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Fri, 10 Jul 2026 06:00:50 -0700 Subject: [PATCH 7/8] Add unstable-internals entry point (#57489) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/react/react-native/pull/57489 See [**RFC0894: Removing deep imports from react-native**](https://github.com/react-native-community/discussions-and-proposals/pull/894) Adds a `'react-native/unstable-internals-do-not-use'` entry point that acts as an explicit, opt-in escape hatch for frameworks and libraries that depend on specific React Native internals. Follows (and supersedes) https://github.com/react-native-community/discussions-and-proposals/pull/985. Resolves T270727304. **Motivation** - Maintains a known list of which React Native internals are still in use. - Decouples removal of the Strict API opt-out from the longer-term decisions about these APIs. - Lets us relocate supporting source files freely, since consumers reference this subpath instead of deep internal paths. **Notes** - Reuses the runtime **object-with-getter pattern** (same as `index.js`), to avoid any native module side effects caused by loading adjacent file exports. - e.g. in Expo, [`hmrUtils.js`](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/src/async-require/hmrUtils.native.ts?fbclid=IwY2xjawS7GjNwZG9mA2V4dG4DYWVtAjExAGJyaWQRMTZ1ckEwU05oWUw2bDBVN21zcnRjBmFwcF9pZAEwAAEen3UuX4Ig-RD3n1ULZIHIHq21Mfk_ci9b2SxquY7UkngDs6qhnRt5YQlb8Os_aem_ylJy40egSOl1qhZ9bA9HTA) is designed to be run early in RN startup — preserve the lazy require characteristic and do not break this. - TypeScript defs on this API are minimal, with complex internal input/handle types reduced to `unknown`. As with D110602194, these are intended to be feature-locked. **Exposed internals and known consumers** NOTE: The exports the community needs have reduced slightly since the spreadsheet in RFC0895 and would almost all align with `devsupport`. However, we're explicitly leaving a decision to formalise until later, and we may need to add in extra APIs quickly based on RC feedback. | Export | Source module | Known consumers | |---|---|---| | `AppContainer` | `Libraries/ReactNative/AppContainer` | react-native-screens ([DebugContainer.tsx](https://github.com/software-mansion/react-native-screens/blob/6f38d76129d40cc8145d06b98f885b508fa7bf2d/src/components/DebugContainer.tsx#L5)) | | `AssetSourceResolver` | `Libraries/Image/AssetSourceResolver` | Re.pack ([IncludeModules.ts](https://github.com/callstack/repack/blob/2498b3a426203daa4b81c7e6f7a88e86d88e77b9/packages/repack/src/modules/IncludeModules.ts#L9)) | | `customDirectEventTypes` | `Libraries/Renderer/shims/ReactNativeViewConfigRegistry` | react-native-gesture-handler ([customDirectEventTypes.ts](https://github.com/software-mansion/react-native-gesture-handler/blob/ee147abf364bef48a02d3cf437572b4dddd30bec/packages/react-native-gesture-handler/src/handlers/customDirectEventTypes.ts#L2)) | | `DevLoadingView` | `Libraries/Utilities/DevLoadingView` | Expo ([hmrUtils.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/src/async-require/hmrUtils.native.ts)), Re.pack ([WebpackHMRClient.ts](https://github.com/callstack/repack/blob/2498b3a426203daa4b81c7e6f7a88e86d88e77b9/packages/repack/src/modules/WebpackHMRClient.ts)) | | `getDevServer` | `Libraries/Core/Devtools/getDevServer` | Expo ([getDevServer.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/src/async-require/getDevServer.native.ts), [hmrUtils.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/src/async-require/hmrUtils.native.ts), [base.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/src/dom/base.ts), [getDevServer.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/metro-runtime/src/getDevServer.native.ts), [devServerEndpoints.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/log-box/src/utils/devServerEndpoints.ts), [getConnectionInfo.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/devtools/src/getConnectionInfo.native.ts), [index.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo-router/src/getDevServer/index.native.ts)) | | `HMRClient` | `Libraries/Utilities/HMRClient` | Expo ([metroServerLogs.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/metro-runtime/src/metroServerLogs.native.ts)) | | `NativeExceptionsManager` | `Libraries/Core/NativeExceptionsManager` | Expo ([hmrUtils.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/src/async-require/hmrUtils.native.ts)), Re.pack ([WebpackHMRClient.ts](https://github.com/callstack/repack/blob/2498b3a426203daa4b81c7e6f7a88e86d88e77b9/packages/repack/src/modules/WebpackHMRClient.ts)) | | `NativeRedBox` | `Libraries/NativeModules/specs/NativeRedBox` | Expo ([hmrUtils.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/src/async-require/hmrUtils.native.ts)), Re.pack ([WebpackHMRClient.ts](https://github.com/callstack/repack/blob/2498b3a426203daa4b81c7e6f7a88e86d88e77b9/packages/repack/src/modules/WebpackHMRClient.ts)) | | `NativeSourceCode` | `Libraries/NativeModules/specs/NativeSourceCode` | Expo ([getBundleUrl.native.ts](https://github.com/expo/expo/blob/2d07e3a17f4be2284bbf579d8825fe32f762ce1a/packages/expo/src/utils/getBundleUrl.native.ts)) | | `PressabilityDebugView` | `Libraries/Pressability/PressabilityDebug` | react-native-gesture-handler ([PressabilityDebugView.tsx](https://github.com/software-mansion/react-native-gesture-handler/blob/ee147abf364bef48a02d3cf437572b4dddd30bec/packages/react-native-gesture-handler/src/handlers/PressabilityDebugView.tsx#L2)) | **Alternative names** - `unstable-frameworks` - `unstable-devsupport` - `frameworks-private-interface` (mirroring `ReactPrivateInterface.js`) — this is likely a future stable shape candidate. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D110911864 fbshipit-source-id: 4cb4758e6621a626916cc6543f9a1f8462f1d5b0 --- packages/react-native/package.json | 5 + .../src/unstable-internals-do-not-use.d.ts | 214 ++++++++++++++++++ .../src/unstable-internals-do-not-use.js | 76 +++++++ 3 files changed, 295 insertions(+) create mode 100644 packages/react-native/src/unstable-internals-do-not-use.d.ts create mode 100644 packages/react-native/src/unstable-internals-do-not-use.js diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 79f333b2af9c..659c5d04a3ac 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -53,6 +53,11 @@ "default": "./src/react-private-interface.js" }, "./setup-env": "./src/setup-env.js", + "./unstable-internals-do-not-use": { + "react-native-unstable-internals": "./src/unstable-internals-do-not-use.d.ts", + "types": null, + "default": "./src/unstable-internals-do-not-use.js" + }, "./src/fb_internal/*": "./src/fb_internal/*", "./package.json": "./package.json" }, diff --git a/packages/react-native/src/unstable-internals-do-not-use.d.ts b/packages/react-native/src/unstable-internals-do-not-use.d.ts new file mode 100644 index 000000000000..6c1f3bf54c40 --- /dev/null +++ b/packages/react-native/src/unstable-internals-do-not-use.d.ts @@ -0,0 +1,214 @@ +/** + * 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. + * + * @format + */ + +// ---------------------------------------------------------------------------- +// Types entry point for react-native/unstable-internals-do-not-use. +// +// IMPORTANT: Keep this file in sync with unstable-internals-do-not-use.js. +// ---------------------------------------------------------------------------- + +import type * as React from 'react'; + +// #region AppContainer + +interface AppContainerProps { + children?: React.ReactNode | undefined; + rootTag: number; + initialProps?: object | undefined; + WrapperComponent?: React.ComponentType | null | undefined; + rootViewStyle?: unknown | undefined; + internal_excludeLogBox?: boolean | undefined; + internal_excludeInspector?: boolean | undefined; +} + +/** Root component that wraps and mounts a React Native app tree. */ +export const AppContainer: React.ComponentType; + +// #endregion +// #region AssetSourceResolver + +interface ResolvedAssetSource { + readonly __packager_asset: boolean; + readonly width: number | null | undefined; + readonly height: number | null | undefined; + readonly uri: string; + readonly scale: number; +} + +/** Resolves a packager asset descriptor to a loadable source for the current platform. */ +export class AssetSourceResolver { + serverUrl: string | null | undefined; + jsbundleUrl: string | null | undefined; + asset: unknown; + constructor( + serverUrl: string | null | undefined, + jsbundleUrl: string | null | undefined, + asset: unknown, + ); + isLoadedFromServer(): boolean; + isLoadedFromFileSystem(): boolean; + defaultAsset(): ResolvedAssetSource; + getAssetUsingResolver(resolver: 'android' | 'generic'): ResolvedAssetSource; + assetServerURL(): ResolvedAssetSource; + scaledAssetPath(): ResolvedAssetSource; + scaledAssetURLNearBundle(): ResolvedAssetSource; + resourceIdentifierWithoutScale(): ResolvedAssetSource; + drawableFolderInBundle(): ResolvedAssetSource; + fromSource(source: string): ResolvedAssetSource; + static pickScale(scales: number[], deviceScale?: number): number; +} + +// #endregion +// #region customDirectEventTypes + +/** Registry mapping custom direct (non-bubbling) event names to their registration names. */ +export const customDirectEventTypes: { + [eventName: string]: Readonly<{ + registrationName: string; + }>; +}; + +// #endregion +// #region DevLoadingView + +/** Dev-only overlay banner showing bundle load, refresh, and error status. */ +export const DevLoadingView: { + showMessage( + message: string, + type: 'load' | 'refresh' | 'error', + options?: {dismissButton?: boolean | undefined}, + ): void; + hide(): void; +}; + +// #endregion +// #region getDevServer + +interface DevServerInfo { + url: string; + fullBundleUrl: string | null; + bundleLoadedFromServer: boolean; +} + +/** Returns information about the running dev server. */ +export function getDevServer(): DevServerInfo; + +// #endregion +// #region HMRClient + +/** Client that receives Fast Refresh updates and applies them at runtime. */ +export class HMRClient { + enable(): void; + disable(): void; + registerBundle(requestUrl: string): void; + log( + level: + | 'trace' + | 'info' + | 'warn' + | 'error' + | 'log' + | 'group' + | 'groupCollapsed' + | 'groupEnd' + | 'debug', + data: ReadonlyArray, + ): void; + setup( + platform: string, + bundleEntry: string, + host: string, + port: number | string, + isEnabled: boolean, + scheme?: string, + ): void; +} + +// #endregion +// #region NativeExceptionsManager + +interface StackFrame { + column: number | null; + file: string | null; + lineNumber: number | null; + methodName: string; + collapse?: boolean | undefined; +} + +interface ExceptionData { + message: string; + originalMessage: string | null; + name: string | null; + componentStack: string | null; + stack: StackFrame[]; + id: number; + isFatal: boolean; + extraData?: object | undefined; +} + +/** Reports JS exceptions to native and manages RedBox. */ +export const NativeExceptionsManager: { + reportFatalException( + message: string, + stack: StackFrame[], + exceptionId: number, + ): void; + reportSoftException( + message: string, + stack: StackFrame[], + exceptionId: number, + ): void; + dismissRedbox(): void; + reportException(data: ExceptionData): void; +}; + +// #endregion +// #region NativeRedBox + +interface NativeRedBoxSpec { + setExtraData(extraData: object, forIdentifier: string): void; + dismiss(): void; +} + +/** Native module for the RedBox error overlay; null when unavailable. */ +export const NativeRedBox: NativeRedBoxSpec | null; + +// #endregion +// #region NativeSourceCode + +interface SourceCodeConstants { + scriptURL: string; +} + +/** Native module exposing source-code constants such as the bundle scriptURL. */ +export const NativeSourceCode: { + getConstants(): SourceCodeConstants; +}; + +// #endregion +// #region PressabilityDebugView + +type Rect = Readonly<{ + bottom?: number | null | undefined; + left?: number | null | undefined; + right?: number | null | undefined; + top?: number | null | undefined; +}>; + +type RectOrSize = Rect | number; + +interface PressabilityDebugViewProps { + color: unknown; + hitSlop: RectOrSize | null | undefined; +} + +/** Debug overlay that visualizes press targets when enabled via the Inspector. */ +export const PressabilityDebugView: React.ComponentType; + +// #endregion diff --git a/packages/react-native/src/unstable-internals-do-not-use.js b/packages/react-native/src/unstable-internals-do-not-use.js new file mode 100644 index 000000000000..20cb19edaec0 --- /dev/null +++ b/packages/react-native/src/unstable-internals-do-not-use.js @@ -0,0 +1,76 @@ +/** + * 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. + * + * @noflow + * @format + */ + +'use strict'; +'use client'; + +// ---------------------------------------------------------------------------- +// react-native/unstable-internals-do-not-use +// +// UNSTABLE WITH NO SEMVER GUARANTEES. +// SHOULD NOT BE DEPENDED ON BY NEW CODE. +// +// This is a secondary entry point for frameworks and libraries that depend on +// specific React Native internals, serving as a compatibility bridge. +// +// Consuming codebases must opt in via tsconfig.json: +// "customConditions": ["react-native-unstable-internals"] +// +// Having this entry point: +// - Maintains a known list of which React Native internals are in use. +// - Enables us to relocate supporting files more freely. +// - Gives us time to decide on the future of these APIs (independent from +// removal of the Strict API opt out). +// +// The long term future of these exports is to formalize/delete them where +// appropriate, and collapse this entry point. +// +// Replaces RFC0985 +// https://github.com/react-native-community/discussions-and-proposals/pull/985 +// where we reviewed internal APIs used in the ecosystem. +// +// IMPORTANT: Keep this file in sync with unstable-internals-do-not-use.d.ts. +// ---------------------------------------------------------------------------- + +// eslint-disable-next-line @react-native/monorepo/no-commonjs-exports +module.exports = { + get AppContainer() { + return require('../Libraries/ReactNative/AppContainer').default; + }, + get AssetSourceResolver() { + return require('../Libraries/Image/AssetSourceResolver').default; + }, + get customDirectEventTypes() { + return require('../Libraries/Renderer/shims/ReactNativeViewConfigRegistry') + .customDirectEventTypes; + }, + get DevLoadingView() { + return require('../Libraries/Utilities/DevLoadingView').default; + }, + get getDevServer() { + return require('../Libraries/Core/Devtools/getDevServer').default; + }, + get HMRClient() { + return require('../Libraries/Utilities/HMRClient').default; + }, + get NativeExceptionsManager() { + return require('../Libraries/Core/NativeExceptionsManager').default; + }, + get NativeRedBox() { + return require('../Libraries/NativeModules/specs/NativeRedBox').default; + }, + get NativeSourceCode() { + return require('../Libraries/NativeModules/specs/NativeSourceCode').default; + }, + get PressabilityDebugView() { + return require('../Libraries/Pressability/PressabilityDebug') + .PressabilityDebugView; + }, +}; From 431ea7c4ee5149947e8f792ff76592865fda0cd2 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Fri, 10 Jul 2026 09:28:17 -0700 Subject: [PATCH 8/8] Enable Strict TS API by default (#57490) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/react/react-native/pull/57490 See [**RFC0894: Removing deep imports from react-native**](https://github.com/react-native-community/discussions-and-proposals/pull/894) This is the **big switch** to enable the Strict TypeScript API (generated types + single index entry point) by default in React Native. **Opt-in → opt-out** After this change, the main `react-native` package resolves its `"types"` entry points only to `types_generated/index.d.ts` — with no other subpaths available. The new `"react-native-legacy-deep-imports"` condition maps to legacy `types/` and `Libraries/*.d.ts` sources. **Impact limitation**: For this stage of rollout, the `"default"` condition continues to resolve to source files. Only TypeScript is affected. **How to opt out** Opposite of today's opt-in, which we will update in [the docs](https://reactnative.dev/docs/strict-typescript-api). Again, the only impact area today is **TypeScript**. ```json5 // tsconfig.json { "extends": "react-native/typescript-config", "compilerOptions": { ... "customConditions": ["react-native-legacy-deep-imports"] } } ``` **Other changes** - Drop `react-native/typescript-config/strict` entry point, update README. - Update `__typetests__`. **Rollout plan** **Target release: 0.87**. This and the contributing stack will be cherry picked for RC1. - We've conducted testing against 100+ real Expo codebases, giving us the confidence that we've reduced breaking changes enough that the vast majority of RN codebases can migrate. - The Strict API includes a number of **intentional breaking changes**, and docs have been kept up to date. - We're shipping a `/migrate-to-strict-api` skill to migrate via agents, see https://github.com/react-native-community/skills/pull/3. **What's improved since 0.80?** Since the initial opt-in launch of the Strict API in 0.80, we've been making continuous improvements over the last year to get our generated types into a widely launchable state. Most notably: - 21+ new/updated root APIs and fixes due to community feedback ([discussion](https://github.com/react-native-community/discussions-and-proposals/discussions/893), [PRs](https://github.com/react/react-native/pulls?q=is%3Apr%20label%3A%22JS%20API%20stabilization%20(1.0)%22%20is%3Aclosed)). - Upstream encapsulation blocker in TypeScript, fixed in 6.0 (https://github.com/react/react-native/issues/53565). - Tailwind/Uniwind compatibility (`interface` types for props). - `*Instance` ref type exports for all built-in components (https://github.com/react-native-community/discussions-and-proposals/pull/1003). - Fixes to previously mistyped, high impact APIs, such as `Appearance`. - New subpath entry points for `asset-registry`, `setup-env`, and others. - Refinements to doc comments/type translation build. **Rollback plan** Revert this diff. IMPORTANT: We'll adopt a policy of **super-eager rollback**, if there are any unsolvable issues during the RC phase. Changelog: [General][Breaking] - React Native's default JavaScript API is now the [Strict TypeScript API](https://reactnative.dev/docs/strict-typescript-api). Use `customConditions: ["react-native-legacy-deep-imports"]` to opt out. Reviewed By: cortinico Differential Revision: D110458670 fbshipit-source-id: 4b0e0b458a5f895f783d6d936e7b11ccff2df076 --- .github/workflows/test-all.yml | 4 ++-- package.json | 4 ++-- .../react-native/__typetests__/tsconfig.json | 7 +++--- .../__typetests__/tsconfig.legacy.json | 10 ++++++++ packages/react-native/package.json | 11 ++++----- packages/react-native/types/tsconfig.json | 17 ------------- packages/typescript-config/README.md | 15 ++++-------- packages/typescript-config/package.json | 3 +-- .../typescript-config/tsconfig.strict.json | 8 ------- packages/virtualized-lists/package.json | 4 ++-- .../js-api/build-types/buildGeneratedTypes.js | 24 ++++--------------- scripts/run-ci-javascript-tests.js | 2 +- 12 files changed, 34 insertions(+), 75 deletions(-) rename scripts/js-api/build-types/templates/tsconfig.test.json => packages/react-native/__typetests__/tsconfig.json (59%) create mode 100644 packages/react-native/__typetests__/tsconfig.legacy.json delete mode 100644 packages/react-native/types/tsconfig.json delete mode 100644 packages/typescript-config/tsconfig.strict.json diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 422a90c712ae..1f49896cbf36 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -471,9 +471,9 @@ jobs: - name: Flow shell: bash run: yarn flow-check - - name: TypeScript + - name: TypeScript (legacy deep imports / manual types) shell: bash - run: yarn test-typescript + run: yarn test-typescript-legacy test_js: runs-on: ubuntu-latest diff --git a/package.json b/package.json index 0f8e035b505e..df2852f1c5a2 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "test-release-local-clean": "node ./scripts/release-testing/test-release-local-clean.js", "test-release-local": "node ./scripts/release-testing/test-release-local.js", "test-ios": "./scripts/objc-test.sh test", - "test-typescript": "tsc -p packages/react-native/types/tsconfig.json", - "test-generated-typescript": "tsc -p packages/react-native/types_generated/tsconfig.test.json", + "test-typescript-legacy": "tsc -p packages/react-native/__typetests__/tsconfig.legacy.json", + "test-generated-typescript": "tsc -p packages/react-native/__typetests__/tsconfig.json", "test": "jest", "fantom": "./scripts/fantom.sh", "fantom-cli": "./scripts/fantom-cli.sh", diff --git a/scripts/js-api/build-types/templates/tsconfig.test.json b/packages/react-native/__typetests__/tsconfig.json similarity index 59% rename from scripts/js-api/build-types/templates/tsconfig.test.json rename to packages/react-native/__typetests__/tsconfig.json index 7af1dd074cb8..241c3b969fb2 100644 --- a/scripts/js-api/build-types/templates/tsconfig.test.json +++ b/packages/react-native/__typetests__/tsconfig.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { "module": "esnext", "lib": ["es2020"], @@ -9,9 +10,7 @@ "jsx": "react", "noEmit": true, "forceConsistentCasingInFileNames": true, - "paths": {"react-native": ["."]}, - "moduleResolution": "bundler", - "customConditions": ["react-native-strict-api"] + "moduleResolution": "bundler" }, - "include": ["**/*.d.ts", "../__typetests__/**/*"] + "include": ["**/*"] } diff --git a/packages/react-native/__typetests__/tsconfig.legacy.json b/packages/react-native/__typetests__/tsconfig.legacy.json new file mode 100644 index 000000000000..3aea5153dceb --- /dev/null +++ b/packages/react-native/__typetests__/tsconfig.legacy.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + // Legacy variant: Opts into the `"react-native-legacy-deep-imports"` export + // condition so `react-native` and `react-native/Libraries/*` resolve to the + // hand-written types in `types/`. + "extends": "./tsconfig.json", + "compilerOptions": { + "customConditions": ["react-native-legacy-deep-imports"] + } +} diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 659c5d04a3ac..c44e6658dec9 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -27,20 +27,19 @@ "react-native": "cli.js" }, "main": "./index.js", - "types": "types", "exports": { ".": { - "react-native-strict-api": "./types_generated/index.d.ts", - "types": "./types/index.d.ts", + "react-native-legacy-deep-imports": "./types/index.d.ts", + "types": "./types_generated/index.d.ts", "default": "./index.js" }, "./Libraries/*": { - "react-native-strict-api": null, - "types": "./Libraries/*.d.ts", + "react-native-legacy-deep-imports": "./Libraries/*.d.ts", + "types": null, "default": "./Libraries/*.js" }, "./Libraries/*.js": { - "react-native-strict-api": null, + "types": null, "default": "./Libraries/*.js" }, "./scripts/*": "./scripts/*", diff --git a/packages/react-native/types/tsconfig.json b/packages/react-native/types/tsconfig.json deleted file mode 100644 index bb7659500b10..000000000000 --- a/packages/react-native/types/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "lib": ["es6"], - "strict": false, - "noImplicitAny": true, - "noImplicitThis": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "types": [], - "jsx": "react", - "noEmit": true, - "forceConsistentCasingInFileNames": true, - "paths": {"react-native": ["."]} - }, - "include": ["**/*.d.ts", "../__typetests__/**/*"] -} diff --git a/packages/typescript-config/README.md b/packages/typescript-config/README.md index d58ee3b56455..bf53f0c39a6a 100644 --- a/packages/typescript-config/README.md +++ b/packages/typescript-config/README.md @@ -9,25 +9,18 @@ This package provides the default `tsconfig.json` used by newly built React Nati This template is customized for specific versions of React Native, and should be updated in sync with the rest of your app. -## Strict TypeScript API +## Opting out of the Strict TypeScript API (default in 0.87) -To opt into the new [strict TypeScript API](https://reactnative.dev/blog/2025/06/12/moving-towards-a-stable-javascript-api#strict-typescript-api-opt-in) you can extend from `@react-native/typescript-config/strict` +To opt out of the new [Strict TypeScript API](https://reactnative.dev/docs/strict-typescript-api) you can extend from `@react-native/typescript-config/strict` -```jsonc -{ - "extends": "@react-native/typescript-config/strict", - // ... -} -``` - -or alternatively add the `customConditions` yourself: +If your app still needs access to deep `'react-native/Libraries/*'` imports (deprecated in 0.80), you can opt out via `customConditions` in your `tsconfig.json`: ```jsonc { "extends": "@react-native/typescript-config", "compilerOptions": { // ... - "customConditions": ["react-native-strict-api", "react-native"] + "customConditions": ["react-native", "react-native-legacy-deep-imports"] } } ``` diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index a9bdc0e273b4..5e2b217934de 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -16,7 +16,6 @@ ], "bugs": "https://github.com/react/react-native/issues", "exports": { - ".": "./tsconfig.json", - "./strict": "./tsconfig.strict.json" + ".": "./tsconfig.json" } } diff --git a/packages/typescript-config/tsconfig.strict.json b/packages/typescript-config/tsconfig.strict.json deleted file mode 100644 index c060fa2a4191..000000000000 --- a/packages/typescript-config/tsconfig.strict.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./tsconfig.json", - "display": "React Native (Strict)", - "compilerOptions": { - "customConditions": ["react-native-strict-api", "react-native"] - } -} diff --git a/packages/virtualized-lists/package.json b/packages/virtualized-lists/package.json index aca2c989aaf6..4ce33c821105 100644 --- a/packages/virtualized-lists/package.json +++ b/packages/virtualized-lists/package.json @@ -21,8 +21,8 @@ }, "exports": { ".": { - "react-native-strict-api": "./types_generated/index.d.ts", - "types": "./index.d.ts", + "react-native-legacy-deep-imports": "./index.d.ts", + "types": "./types_generated/index.d.ts", "default": "./index.js" }, "./*": { diff --git a/scripts/js-api/build-types/buildGeneratedTypes.js b/scripts/js-api/build-types/buildGeneratedTypes.js index 1408b1625b50..3ef71abc2d13 100644 --- a/scripts/js-api/build-types/buildGeneratedTypes.js +++ b/scripts/js-api/build-types/buildGeneratedTypes.js @@ -55,26 +55,10 @@ async function buildGeneratedTypes(): Promise> { } } - await Promise.all([ - fs.copyFile( - path.join(__dirname, 'templates', 'tsconfig.json'), - path.join( - PACKAGES_DIR, - 'react-native', - TYPES_OUTPUT_DIR, - 'tsconfig.json', - ), - ), - fs.copyFile( - path.join(__dirname, 'templates', 'tsconfig.test.json'), - path.join( - PACKAGES_DIR, - 'react-native', - TYPES_OUTPUT_DIR, - 'tsconfig.test.json', - ), - ), - ]); + await fs.copyFile( + path.join(__dirname, 'templates', 'tsconfig.json'), + path.join(PACKAGES_DIR, 'react-native', TYPES_OUTPUT_DIR, 'tsconfig.json'), + ); if (allErrors.length > 0) { console.error( diff --git a/scripts/run-ci-javascript-tests.js b/scripts/run-ci-javascript-tests.js index 7992b46fbcce..aed03f7f812a 100644 --- a/scripts/run-ci-javascript-tests.js +++ b/scripts/run-ci-javascript-tests.js @@ -81,7 +81,7 @@ try { ); describe('Test: TypeScript tests'); - execAndLog(`${YARN_BINARY} run test-typescript`); + execAndLog(`${YARN_BINARY} run test-typescript-legacy`); } catch (e) { if (e instanceof ExecError) { console.error(e.message);