Repository navigation
Use metro:runtime for asyncRequireModulePath so Metro's transform cache is shareable across checkouts - #58584
Conversation
20e2708 to
4da2b4d
Compare
|
What would it take to allow |
4da2b4d to
dbc06cf
Compare
|
@javache The other option that works is using + extraNodeModules: {
+ 'metro-runtime': path.dirname(
+ require.resolve('metro-runtime/package.json'),
+ ),
+ },in the default metro config, but I thought adding the file seemed like a better idea since we do it for other configs (although as you said it is to access internal RN module instead of other package). |
|
@rubennorte @motiz88 Little ping on this :) |
|
I don't think this approach is safe unfortunately. Currently, Absolute paths obviously work because they're not sensitive to the origin, but no relative or bare specifier is safe on its own - under isolated node modules neither Expo gets away with it because the specifier is intercepted by a custom resolver and pointed at a known location, but on the default resolver this PR would break folks.
Otherwise, I think the cleanest approach is to use the new Separately, if cache portability has indeed been accidentally broken by this for most users, I'd want to make sure fixing cache re-use doesn't inadvertently expose issues with common setups. |
|
I've opened react/metro#2083 to allow us to point at |
dbc06cf to
bbace1d
Compare
…he 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. Point it at metro:runtime/modules/asyncRequire instead: Metro's built-in metro: scheme resolver maps it to the metro-runtime Metro itself depends on, from any importer, so it also works for library code that cannot see metro-runtime under an isolated node_modules layout. Requires Metro with react/metro#2083.
bbace1d to
7c18d80
Compare
|
@robhogan Updated to use the new metro api, so it should be good to go when it releases. I tested locally by packing it. I also verified with |
|
The only thing I saw was |
|
Blocked on metro release. |
Summary:
@react-native/metro-configsetsasyncRequireModulePathwithrequire.resolve(), an absolute path into the current checkout. Metro hashes the whole transformer config into its global transform cache key (metro-transform-workergetCacheKey), so two checkouts of the same project (git worktrees, CI workspaces, a renamed directory) never share a transform cache entry. Nothing fails; the cache is just cold for every checkout.A bare specifier is not an option: Metro inlines it into every module with an
import(), including library code, and under an isolatednode_moduleslayout a library cannot seemetro-runtimeorreact-native(see the resolution table below).This uses
metro:runtime/modules/asyncRequireinstead, added in react/metro#2083. Metro's built-inmetro:scheme resolver maps it to themetro-runtimethat Metro itself depends on, from any importer, likemetro:babel-runtime. The string is checkout-independent, so the cache key is too.@react-native/metro-configno longer requires anything frommetro-runtime, so the dependency is dropped (Metro resolvesmetro:runtimefrom its own copy).Requires a Metro release that includes react/metro#2083. There is none yet (latest is 0.87.1); on current Metro the scheme resolver throws
Unsupported 'metro:' pathnamefor every module with animport(). This should land together with themetro-configdependency bump to that release.__source.fileName(_jsxFileName, from@babel/plugin-transform-react-jsx-source, which the preset enables in dev) embeds the absolute path of the checkout that transformed the module, so checkout B served from A's cache shows A's paths there (73 modules in the template app; source maps are unaffected). Metro's own default config and Expo already share caches this way. I checkedbabel-plugin-module-resolver(rootandalias), which emits relative paths, so its output is identical across checkouts.metro:runtime/...before the scheme resolver does (metro-resolverresolve). One that handles every non-relative specifier itself instead of delegating tocontext.resolveRequestwould need to passmetro:through, same as formetro:babel-runtime.Changelog:
[GENERAL] [FIXED] - Make Metro's transform cache shareable across checkouts by pointing
asyncRequireModulePathatmetro:runtime/modules/asyncRequireinstead of an absolute pathTest Plan:
All tests use Metro built from
mainat 3de6a03 (the commit of react/metro#2083), packed as0.87.1tarballs and installed over everymetro*package viaoverrides, and@react-native/metro-config@0.88.0with this change applied to the installed file.Cache sharing across checkouts. A fresh
@react-native-community/cli initapp on 0.88.0 copied to a second directory, both using oneFileStorecache dir,react-native bundle(dev, iOS) in A with--reset-cacheand then in B, with the file map already warm:require.resolveB's bundle from the shared cache is byte-identical to a cold B bundle except for the
_jsxFileNamestrings above. Withbabel-plugin-module-resolver(root: ['./src'],alias: {'@app': './src'}) in the Babel config, A and B cold bundles differ only in those same strings.Resolution from library code. A pnpm workspace with
nodeLinker: isolatedandhoist: false:apps/appdepends onreact-native@0.88.0and a workspace packagepackages/lazy-libwhoseindex.jsdoesimport('./lazy')and declares no dependencies.Metro.runBuildof the app withgetDefaultConfigand eachasyncRequireModulePath:asyncRequireModulePathrequire.resolve()(current)metro-runtime/src/modules/asyncRequire(Metro's default)Unable to resolve module metro-runtime/src/modules/asyncRequire from <repo>/packages/lazy-lib/index.jsreact-native/asset-registry(probe for areact-native/*entry point)Unable to resolve module react-native/asset-registry from <repo>/packages/lazy-lib/index.jsmetro:runtime/modules/asyncRequire(this PR)End to end. The same template app with
App.tsxrenderingReact.lazy(() => import('./src-lazy')), built and launched on an iOS simulator againstreact-native start. Metro serves the main bundle withnode_modules/metro-runtime/src/modules/asyncRequire.jsinlined, thensrc-lazy.bundleon demand, and the lazy component renders with no errors in the Metro or app logs.Cache key. The transformer-config segment of Metro's global cache key (
metro-transform-workergetCacheKey) for two checkouts at/Users/x/app-aand/Users/x/app-b: