From dbc06cf8ec7fccbd6cff54febaa1c1dbbd56e0b6 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Thu, 17 Sep 2026 22:24:17 -0400 Subject: [PATCH] Use a module request for asyncRequireModulePath so Metro's transform cache is shareable across checkouts @react-native/metro-config set asyncRequireModulePath with require.resolve(), which embeds the absolute path of the checkout in Metro's global transform cache key. Two checkouts of the same project (git worktrees, CI workspaces) never shared a transform cache entry. Add a react-native/async-require secondary entry point, like react-native/asset-registry, and point the config at it: a module request that resolves from any module in the project, hoisted or not, because react-native is the app's direct dependency and depends on metro-runtime itself. --- .../no-deep-imports.js | 1 + packages/metro-config/src/index.flow.js | 3 +-- packages/react-native/package.json | 4 +++ packages/react-native/src/async-require.js | 27 +++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 packages/react-native/src/async-require.js diff --git a/packages/eslint-plugin-react-native/no-deep-imports.js b/packages/eslint-plugin-react-native/no-deep-imports.js index 50639ee29035..4f163f1c16c4 100644 --- a/packages/eslint-plugin-react-native/no-deep-imports.js +++ b/packages/eslint-plugin-react-native/no-deep-imports.js @@ -201,6 +201,7 @@ module.exports = { return ( source.value === 'react-native/asset-registry' || + source.value === 'react-native/async-require' || source.value === 'react-native/react-private-interface' || source.value === 'react-native/setup-env' || source.value === 'react-native/unstable-internals-do-not-use' diff --git a/packages/metro-config/src/index.flow.js b/packages/metro-config/src/index.flow.js index 5fb4be2cd3fe..14c0cfef971a 100644 --- a/packages/metro-config/src/index.flow.js +++ b/packages/metro-config/src/index.flow.js @@ -85,8 +85,7 @@ export function getDefaultConfig(projectRoot: string): ConfigT { transformer: { allowOptionalDependencies: true, assetRegistryPath: 'react-native/asset-registry', - asyncRequireModulePath: - require.resolve('metro-runtime/src/modules/asyncRequire'), + asyncRequireModulePath: 'react-native/async-require', babelTransformerPath: require.resolve('@react-native/metro-babel-transformer'), getTransformOptions: async () => ({ diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 2242dfb096b7..6d6d1e390e0f 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -49,6 +49,10 @@ "types": null, "default": "./src/asset-registry.js" }, + "./async-require": { + "types": null, + "default": "./src/async-require.js" + }, "./react-private-interface": { "types": null, "default": "./src/react-private-interface.js" diff --git a/packages/react-native/src/async-require.js b/packages/react-native/src/async-require.js new file mode 100644 index 000000000000..a6393c470da6 --- /dev/null +++ b/packages/react-native/src/async-require.js @@ -0,0 +1,27 @@ +/** + * 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/async-require +// +// This is an untyped secondary entry point intended to be referenced from +// Metro's `transformer.asyncRequireModulePath` config option. Metro inlines +// it into every module that uses `import()`, so it must be a module request +// that resolves from any module in the project. +// +// Apps/libraries should not import this module; use `import()` instead. +// ---------------------------------------------------------------------------- + +const asyncRequire = require('metro-runtime/src/modules/asyncRequire'); + +// eslint-disable-next-line @react-native/monorepo/no-commonjs-exports +module.exports = asyncRequire;