Skip to content

chore: support Yarn's pnpm nodeLinker#3010

Merged
Saadnajmi merged 10 commits into
microsoft:mainfrom
Saadnajmi:saadnajmi/yarn-pnpm-mode
Jul 8, 2026
Merged

chore: support Yarn's pnpm nodeLinker#3010
Saadnajmi merged 10 commits into
microsoft:mainfrom
Saadnajmi:saadnajmi/yarn-pnpm-mode

Conversation

@Saadnajmi

@Saadnajmi Saadnajmi commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Switch Yarn's nodeLinker from node-modules to pnpm and make the monorepo build, lint, type-check, and bundle under the pnpm linker's strict (non-hoisted) node_modules layout. That layout surfaces phantom dependencies that previously resolved only via hoisting; this PR declares them, keeping upstream-tracked manifests as pristine as possible.

RNTester bundles for macOS (dev + production), and the fork's PR workflow is green: yarn install --immutable, constraints, test-ci (Jest), flow-check, lint, format-check, and Build RNTester.

Changed files (audited for minimal divergence)

File Why [macOS] tag
.yarnrc.yml nodeLinker: pnpm + a packageExtensions block declaring the per-package phantom deps in one fork-only place (so upstream manifests stay pristine) yes
.flowconfig module.name_mappers so @react-native/fantom / @react-native/core-cli-utils self-references resolve under pnpm yes
packages/rn-tester/metro.config.js one-line extraNodeModules alias for the react-nativereact-native-macos redirect (not self-resolvable under pnpm) yes
docsite/.yarnrc.yml pin the standalone docsite to node-modules so the root pnpm setting doesn't break its Docusaurus build n/a (config)
.prettierrc list prettier-plugin-hermes-parser explicitly; Prettier 2.x auto-loaded it from a hoisted node_modules, which pnpm doesn't provide n/a (JSON)
package.json declare root dev-tooling deps (eslint config/plugins, @babel/* for scripts/build-types, metro/metro-config, yargs, etc.) that root configs/scripts use directly n/a (JSON)
yarn.lock mechanical n/a (generated)

Design notes

  • packageExtensions for per-package shims. The per-package phantom deps (metro, metro-config, metro-babel-transformer, invariant, hermes-estree, jest-docblock, jest-message-util, source-map, @babel/parser, @babel/plugin-syntax-flow) are declared centrally in .yarnrc.yml rather than editing seven individual manifests — verified that packageExtensions applies to workspace packages in Yarn 4.12.
  • Only third-party deps go in packageExtensions. The fork's yarn.config.cjs constraints inspect manifest deps and don't see packageExtensions entries; on release branches they pin @react-native/* deps to the release version. So no @react-native/* deps belong in packageExtensions — they'd escape that governance. (Verified via an A/B test on a simulated 0.83-stable tree.)
  • Scoped to what the fork actually runs. Changes that only served the upstream Test All workflow (the yarn-install composite action, code-analysis-bot, the ReactNativeApi.d.ts API snapshot) were intentionally left untouched — the fork's PR workflow doesn't use them.

Upstreamable subset

A few are genuine undeclared direct dependencies upstream too (masked there by Yarn 1 hoisting) and could be separate PRs to facebook/react-native — most cleanly @react-native/metro-babel-transformer declaring metro-babel-transformer. @react-native/codegen's @babel/parser is already declared upstream.

Test plan

yarn install
cd packages/rn-tester
node ../react-native/cli.js bundle --platform macos --dev true \
  --entry-file js/RNTesterApp.macos.js --bundle-output /tmp/RNTesterApp.macos.jsbundle
# plus: yarn constraints && yarn flow-check && yarn lint && yarn format-check && yarn test-ci

🤖 Generated with Copilot CLI

@Saadnajmi Saadnajmi requested a review from a team as a code owner July 7, 2026 07:52
@Saadnajmi Saadnajmi force-pushed the saadnajmi/yarn-pnpm-mode branch 3 times, most recently from 3ef3eb8 to eef9a9a Compare July 7, 2026 09:03
@Saadnajmi Saadnajmi force-pushed the saadnajmi/yarn-pnpm-mode branch 4 times, most recently from e17d24f to 88a7cd3 Compare July 7, 2026 10:18
Saadnajmi and others added 5 commits July 7, 2026 10:23
Switch the monorepo to Yarn's pnpm linker (strict, non-hoisted node_modules).
Pin the standalone docsite to nodeLinker: node-modules so this setting does not
leak into its Docusaurus build, which relies on hoisted resolution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… pnpm

Under the pnpm linker's strict layout, previously-hoisted dependencies must be
declared explicitly:

- rn-tester metro config imports mergeConfig from @react-native/metro-config
  instead of requiring metro-config directly, and maps the react-native-macos
  self-reference to the workspace via extraNodeModules (the react-native ->
  react-native-macos redirect is not self-resolvable under pnpm).
- Declare @react-native/babel-preset and @babel/runtime at the monorepo root.
- Declare @babel/parser in @react-native/codegen, which its built lib requires.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The pnpm linker's strict layout requires declaring dependencies that the JS
toolchain previously resolved through a hoisted node_modules.

ESLint:
- Declare @react-native/eslint-config and the plugins @react-native/eslint-plugin,
  @react-native/eslint-plugin-monorepo, @react-native/eslint-plugin-specs, plus
  @typescript-eslint/eslint-plugin, eslint-plugin-react-hooks and the
  jsonc-eslint-parser referenced by the root .eslintrc.
- Declare hermes-estree in @react-native/eslint-plugin-monorepo, which its
  rules require.

Prettier:
- List prettier-plugin-hermes-parser explicitly in .prettierrc plugins; Prettier
  2.x auto-loaded it from a hoisted node_modules, which pnpm does not provide.

Flow:
- Declare @babel/parser, @babel/traverse, @babel/types, hermes-estree, yargs,
  metro and metro-config at the root (used by scripts/build-types and helloworld).
- Declare the direct dependencies each package imports: @babel/plugin-syntax-flow
  (babel-plugin-codegen), metro (core-cli-utils), metro-babel-transformer
  (metro-babel-transformer), invariant (compatibility-check), and metro/
  metro-config/jest-docblock/jest-message-util/source-map (fantom).
- Add Flow module.name_mapper entries so @react-native/fantom and
  @react-native/core-cli-utils (including self-references) resolve under pnpm.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pshot

Under the pnpm linker, API Extractor emitted module specifiers via their on-disk
(symlinked) path, e.g. .../node_modules/@react-native-macos/virtualized-lists.
Normalize these back to a bare package name in the generated snapshot so it is
deterministic regardless of linker, and regenerate the (previously stale)
ReactNativeApi.d.ts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- yarn-install action uses 'yarn install --immutable' instead of the deprecated
  '--non-interactive --frozen-lockfile'. The --non-interactive flag silently
  exits 1 under the pnpm linker; --immutable is Yarn's recommended replacement
  and works with both linkers.
- code-analysis-bot filters blank lines so stray output under the pnpm linker
  does not shift its converter/JSON stream pairing and break parsing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Saadnajmi Saadnajmi force-pushed the saadnajmi/yarn-pnpm-mode branch from 88a7cd3 to 1487d1d Compare July 7, 2026 17:25
Comment thread .github/actions/yarn-install/action.yml Outdated
Comment thread docsite/.yarnrc.yml Outdated
Saadnajmi and others added 3 commits July 7, 2026 14:26
Move the per-package phantom-dependency declarations out of individual
package.json manifests and into a single packageExtensions block in .yarnrc.yml.
This reverts seven upstream-tracked manifests to pristine, confining the
pnpm-compat shims to the fork-only .yarnrc.yml so upstream merges stay clean.

Only third-party deps are moved. The fork's yarn.config.cjs constraints inspect
manifest deps and do not see packageExtensions entries; on release branches they
pin @react-native/* deps to the release version, so any @react-native/* dep must
remain in a manifest to stay governed. None of the moved deps are @react-native/*.

Moved:
- @react-native/codegen -> @babel/parser
- @react-native/babel-plugin-codegen -> @babel/plugin-syntax-flow
- @react-native/core-cli-utils -> metro
- @react-native/metro-babel-transformer -> metro-babel-transformer
- @react-native/compatibility-check -> invariant
- @react-native/eslint-plugin-monorepo -> hermes-estree
- @react-native/fantom -> jest-docblock, jest-message-util, metro, metro-config, source-map

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Revert the upstream-inherited yarn-install action and code-analysis-bot
  changes. Those files only serve the upstream Test All workflow, which the
  fork does not use for gating; the fork's PR workflow installs with
  'yarn install --immutable' and runs yarn lint/flow-check/format-check
  directly, so no changes to the upstream CI flow are needed.
- Trim the verbose docsite/.yarnrc.yml comment to a single line.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Audit of the pnpm branch to reduce divergence from upstream:

- Drop the ReactNativeApi.d.ts regeneration and the BuildApiSnapshot.js import-
  path normalization. The API snapshot is only validated by the upstream
  Test All workflow, which the fork does not use; its PR workflow never runs
  build-types --validate. Reverting them returns two files to pristine (-893).
- Shrink packages/rn-tester/metro.config.js to a single [macOS]-tagged line:
  keep upstream's 'metro-config' require and instead declare metro-config for
  @react-native/tester via packageExtensions, dropping the mergeConfig import
  rewrite.
- Tag the fork-only nodeLinker line with [macOS].
- Drop the [macOS] tag from the docsite .yarnrc.yml comment (config-only file).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Saadnajmi Saadnajmi changed the title chore: make the monorepo bundle under Yarn's pnpm linker chore: support Yarn's pnpm nodeLinker Jul 7, 2026
Comment thread packages/rn-tester/metro.config.js Outdated
Comment thread .flowconfig Outdated
…de wording

- Fence the .flowconfig name_mapper block and the .yarnrc.yml packageExtensions
  block with [macOS] / macOS] start/end tags per the fork convention.
- Reword the metro.config.js alias comment to use 'yarn pnpm mode' terminology.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fantom uses the synchronous SourceMapConsumer constructor (the 0.6.x API);
source-map 0.7+ returns a Promise. The ^0.7.4 shim resolved fantom to 0.7.6,
which would break its stack-trace symbolication at runtime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Saadnajmi Saadnajmi merged commit 269455f into microsoft:main Jul 8, 2026
31 checks passed
@Saadnajmi Saadnajmi deleted the saadnajmi/yarn-pnpm-mode branch July 8, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants