Repository navigation
Conversation
Currently, Metro reads `"exports"` from the `package.json` closest to the requested subpath, so a nested `{"type": "commonjs"}` or legacy shim hides the root's `"exports"`. That's different to [Node's `PACKAGE_RESOLVE`](https://nodejs.org/api/esm.html#resolution-algorithm-specification), webpack 5 and Vite 8.
This adds `resolver.unstable_resolveExportsFromPackageRoot`, off by default, which reads `"exports"` from the root of the package named by a bare specifier. If there's no `package.json` there, we use the closest as we do today. `"browser"` redirections, `"imports"` and self-references keep using the closest, which is correct for those.
Being off, this can go out in 0.87.x without changing what anyone resolves. We'll enable it by default for 0.88 in a follow-up.
Refs #2153
Changelog:
```
- **[Experimental]**: Add `resolver.unstable_resolveExportsFromPackageRoot` to read `"exports"` from the package root, instead of the closest `package.json`
```
Test plan:
New unit tests.
AI-driven comparison: import graph walks through `DependencyGraph` of Bluesky (iOS, Android, web) and Mattermost (iOS, Android). With the flag off, no edges, failures or warnings differ from main. With it on, 29 edges change on each Mattermost platform and 2 on Bluesky web, as described in the issue, and no failures or warnings differ.
Not covered E2E: a symlinked (pnpm) package with a nested `package.json`. No edges changed in the pnpm app I walked, so that relies on `getPackageForModule` returning the real `rootPath`.
Metro review: Looks goodRisk: low · Commit: This adds an off-by-default No findings. Verified
Benchmarks (base vs this PR)
Method
Not run: The Bluesky and Mattermost comparison in the test plan wasn't repeated, because it needs those apps and the PR's harness. Flag-off equivalence rests on the 36-specifier fixture, the existing tests and code tracing. Real Windows wasn't available, so only mocked win32 in resolver-test.js ran. Real-package checks only resolved entry points and searched source files; nothing was bundled. A few compound shell commands were denied by the session's permission check, so I reran the checks as simple commands or read earlier review output instead. Automated review by Claude. Advisory only: it never approves or blocks, and a maintainer makes the call. |
huntie
left a comment
There was a problem hiding this comment.
Package exports never ends 😅👏🏻
…default This enables `resolver.unstable_applyBrowserSpecAfterExports` (#2134) and `resolver.unstable_resolveExportsFromPackageRoot` (#2160) by default for 0.88. Both shipped off in 0.87.3. For a bare specifier, `"exports"` now comes from the `package.json` at the root of the named package, as in Node, webpack 5 and Vite 8, so a nested `package.json` no longer hides it. An object-valued `"browser"` (or `"react-native"`) field now redirects the file that `"exports"` resolves to, rather than the requested subpath. Some packages rely on the old behaviour to reach a build that works on native: - `@mswjs/interceptors/fetch` (0.41) moves from its browser build to its Node build. - `react-dom/server` moves from `server.browser.js` to `server.node.js`. `react-dom/server.browser` is still exported. - `import 'nanoid/async'` (nanoid 3) moves to `index.native.js`. Either option can be set to `false` to restore the previous behaviour. The options themselves stay for now. Fixes #1135, fixes #1517, fixes #1582, fixes #2153. ## Changelog ``` - **[Breaking]**: Resolver: Read `"exports"` from the package root, and apply `"browser"` redirections to the resolved file, by default (`resolver.unstable_resolveExportsFromPackageRoot`, `resolver.unstable_applyBrowserSpecAfterExports`) ``` ## Test plan Existing tests pass unchanged. AI-driven import graph walks through `DependencyGraph`, with both options off (as `main`) and on, using this branch and RN/Expo `mainFields` and conditions: | App, platform | Edges off -> on | Changed edges | New failures | Warnings off -> on | |---|---|---|---|---| | Bluesky iOS | 25,081 -> 25,081 | 0 | 0 | 1 -> 0 | | Bluesky Android | 24,981 -> 24,981 | 0 | 0 | 1 -> 0 | | Bluesky web | 25,343 -> 25,343 | 2 | 0 | 2 -> 1 | | Mattermost iOS | 13,843 -> 13,961 | 29 | 0 | 120 -> 120 | | Mattermost Android | 13,816 -> 13,934 | 29 | 0 | 120 -> 120 | | pnpm fixture iOS | 5,977 -> 5,977 | 0 | 0 | 1 -> 1 | The changed edges are `@floating-ui/utils/dom` to its `"exports"` target on Bluesky web, and on Mattermost `rxjs/operators` from `dist/cjs` to `dist/esm5` (matching `rxjs`) and `nanoid/non-secure` from `index.js` to `index.cjs`. The walks parse imports with Babel, so they miss transform-injected dependencies, and nothing has run on a device.
Currently, Metro reads
"exports"from thepackage.jsonclosest to the requested subpath, so a nested{"type": "commonjs"}or legacy shim hides the root's"exports". That's different to Node'sPACKAGE_RESOLVE, webpack 5 and Vite 8.This adds
resolver.unstable_resolveExportsFromPackageRoot, off by default, which reads"exports"from the root of the package named by a bare specifier, following Node. The subpath is the literal text after the package name, sopkg/./subandpkg/node_modules/dep/fooaren't exported - we warn and fall back to file-based resolution, as for any other unexported subpath. A package with nopackage.jsonat its root has no"exports", so a nested one doesn't apply. Haste packages andextraNodeModulesmappings have no Node equivalent, and read the root of the mapped directory too, using the closest package if it has nopackage.json."browser"redirections,"imports"and self-references keep using the closest, which is correct for those.Being off, this can go out in 0.87.x without changing what anyone resolves. We'll enable it by default for 0.88 in a follow-up.
Refs #2153
Changelog:
Test plan:
New unit tests.
AI-driven comparison: import graph walks through
DependencyGraphof Bluesky (iOS, Android, web) and Mattermost (iOS, Android). With the flag off, no edges, failures or warnings differ from main. With it on, 29 edges change on each Mattermost platform and 2 on Bluesky web, as described in the issue, and no failures or warnings differ.