Skip to content

Fix jest-preset resolution under pnpm and Yarn pnpm-mode (#58598) - #58598

Open
amroaltah wants to merge 1 commit into
react:mainfrom
amroaltah:export-D119701713
Open

amroaltah wants to merge 1 commit into
react:mainfrom
amroaltah:export-D119701713

Conversation

@amroaltah

@amroaltah amroaltah commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Summary:

Fixes #56641

The preset failed in two ways under strict-isolation installs (pnpm and Yarn pnpm-mode): react-native was not a declared dependency so jest-preset.js could not resolve it, and the transformIgnorePatterns rule only matched classic node_modules layouts so preset sources shipped untransformed.

  • Declare react-native as a peer dependency so the installer links it into the preset's scope.
  • Move babel/core from dependencies to peerDependencies: babel-jest peer-depends on it, so it must be provided, but as a direct dependency a strict installer gives the preset its own copy and the consumer's babel.config.js presets would then load under a different babel/core instance than the consumer's own. A peer keeps one copy, matching how react and react-native are already declared. babel/runtime stays a direct dependency: the preset's sources are compiled with babel/plugin-transform-runtime helpers enabled, so the transformed jest/setup.js requires babel/runtime/helpers/* from the preset's own scope at Jest runtime (verified: removing it makes the pnpm harness fail with Cannot find module 'babel/runtime/helpers/interopRequireDefault').
  • Resolve the babel-jest transformer from the preset's own scope via require.resolve('babel-jest') instead of the bare specifier.
  • Match react-native sources in transformIgnorePatterns at each layout's anchored location - classic node_modules, pnpm (.pnpm/<id>/node_modules/...), and Yarn pnpm-mode (.store/<flat>-npm-<version>-<hash>/package/... (or <flat>-virtual-<hash>/package/... for packages declaring peer dependencies, verified against real Yarn installs)) - so strict-isolation layouts still transform preset and react-native sources. The prefixes are anchored rather than permitting arbitrary depth, so scoped third-party packages whose unscoped name is exactly react-native (sentry/react-native, notifee/react-native), nested directories literally named react-native, and real -suffix packages all stay ignored exactly as before.
  • Write the .store scoped-package segment as (?:-[^-\/]+)* rather than (-[^\/]+)*. The inner class in the original form could itself consume -, so a dash-separated name had exponentially many ways to be partitioned and any near-miss path under node_modules/.store/react-native-... forced catastrophic backtracking. Jest evaluates transformIgnorePatterns against every candidate file path, so one pathological path could hang a run. Restricting the segment to non-dash characters makes the partition unique and the match linear, with no change to which paths are ignored.

Known limitation: under Yarn pnpm-mode's .store layout, a scoped package's slash flattens to a dash, erasing the scope boundary - so third-party react-native-<scope>/* packages (e.g. react-native-async-storage/async-storage) are indistinguishable from genuine react-native/* ones and are also transformed there. Transforming is the safe direction (a miss would ship untransformed sources); the impact is performance-only and confined to Yarn pnpm-mode.

Changelog:
[General][Fixed] - Fix react-native/jest-preset failing to resolve react-native and to transform preset sources under pnpm and Yarn pnpm-mode installs

Differential Revision: D119701713

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 18, 2026
@meta-codesync

meta-codesync Bot commented Sep 18, 2026

Copy link
Copy Markdown

@amroaltah has exported this pull request. If you are a Meta employee, you can view the originating Diff in D119701713.

Summary:

Fixes react#56641

The preset failed in two ways under strict-isolation installs (pnpm and Yarn pnpm-mode): `react-native` was not a declared dependency so `jest-preset.js` could not resolve it, and the `transformIgnorePatterns` rule only matched classic `node_modules` layouts so preset sources shipped untransformed.

- Declare `react-native` as a peer dependency so the installer links it into the preset's scope.
- Move `babel/core` from dependencies to peerDependencies: `babel-jest` peer-depends on it, so it must be provided, but as a direct dependency a strict installer gives the preset its own copy and the consumer's `babel.config.js` presets would then load under a different `babel/core` instance than the consumer's own. A peer keeps one copy, matching how `react` and `react-native` are already declared. `babel/runtime` stays a direct dependency: the preset's sources are compiled with `babel/plugin-transform-runtime` helpers enabled, so the transformed `jest/setup.js` requires `babel/runtime/helpers/*` from the preset's own scope at Jest runtime (verified: removing it makes the pnpm harness fail with `Cannot find module 'babel/runtime/helpers/interopRequireDefault'`).
- Resolve the `babel-jest` transformer from the preset's own scope via `require.resolve('babel-jest')` instead of the bare specifier.
- Match `react-native` sources in `transformIgnorePatterns` at each layout's anchored location - classic `node_modules`, pnpm (`.pnpm/<id>/node_modules/...`), and Yarn pnpm-mode (`.store/<flat>-npm-<version>-<hash>/package/...` (or `<flat>-virtual-<hash>/package/...` for packages declaring peer dependencies, verified against real Yarn installs)) - so strict-isolation layouts still transform preset and `react-native` sources. The prefixes are anchored rather than permitting arbitrary depth, so scoped third-party packages whose unscoped name is exactly `react-native` (`sentry/react-native`, `notifee/react-native`), nested directories literally named `react-native`, and real `-suffix` packages all stay ignored exactly as before.
- Write the `.store` scoped-package segment as `(?:-[^-\/]+)*` rather than `(-[^\/]+)*`. The inner class in the original form could itself consume `-`, so a dash-separated name had exponentially many ways to be partitioned and any near-miss path under `node_modules/.store/react-native-...` forced catastrophic backtracking. Jest evaluates `transformIgnorePatterns` against every candidate file path, so one pathological path could hang a run. Restricting the segment to non-dash characters makes the partition unique and the match linear, with no change to which paths are ignored.

Known limitation: under Yarn pnpm-mode's `.store` layout, a scoped package's slash flattens to a dash, erasing the scope boundary - so third-party `react-native-<scope>/*` packages (e.g. `react-native-async-storage/async-storage`) are indistinguishable from genuine `react-native/*` ones and are also transformed there. Transforming is the safe direction (a miss would ship untransformed sources); the impact is performance-only and confined to Yarn pnpm-mode.

Changelog:
[General][Fixed] - Fix `react-native/jest-preset` failing to resolve `react-native` and to transform preset sources under pnpm and Yarn pnpm-mode installs

Differential Revision: D119701713
@meta-codesync meta-codesync Bot changed the title Fix jest-preset resolution under pnpm and Yarn pnpm-mode Fix jest-preset resolution under pnpm and Yarn pnpm-mode (#58598) Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@react-native/jest fails to resolve react-native in monorepos

1 participant