Adopting Yarn Plug'n'Play on Node.js 20: what breaks when node_modules disappears?
0 reputation · 04 Apr 2025, 14:24 UTC
0 reputation · 04 Apr 2025, 14:24 UTC
Our team is evaluating a migration from Yarn Classic's node_modules layout to Yarn 4 with Plug'n'Play (PnP) resolution, running on Node.js 20. PnP replaces the node_modules directory with a .pnp.cjs loader and a content-addressable cache, which changes how every dependency is resolved at runtime.
The main concern is compatibility with legacy packages and tooling that assume a physical node_modules tree. Some build tools, test runners, and packages that ship prebuilt binaries resolve files via hardcoded node_modules paths, and PnP's strict resolution also surfaces undeclared (phantom) dependencies that node_modules silently tolerated. Yarn offers node-modules linker fallback and packageExtensions as escape hatches, but it is unclear how much patching a mid-size monorepo typically requires.
Before committing to the switch, we want to scope the risk:
29775 reputation · 05 Apr 2025, 00:03 UTC
Moving to Yarn 4 Plug'n'Play (PnP) replaces the physical node_modules tree with a static mapping file (.pnp.cjs). This breaks any tool or package that relies on filesystem-based module resolution rather than the Node.js resolution algorithm.
node_modules directory to locate headers or write compiled artifacts during postinstall. If the build script uses hardcoded relative paths to node_modules, the installation will fail.fs.readFileSync('node_modules/package/package.json')) will throw "File not found" errors.node_modules hoisting), PnP will block the access to Package C.In Yarn 4, you cannot mix linkers within a single package, but you can configure the linker globally or use packageExtensions to fix metadata. To handle problematic packages that absolutely require a physical tree, you have two primary options:
nodeLinker: node-modules in .yarnrc.yml. This disables PnP entirely for the workspace, returning to the classic layout.packageExtensions in .yarnrc.yml to manually add missing peer dependencies or dependencies that were previously hoisted. This allows you to keep PnP active while satisfying the strict resolution requirements of legacy packages.Caching node_modules is obsolete under PnP. Your CI pipeline must be updated to cache the following instead:
.yarn/cache (the content-addressable store)..pnp.cjs and .pnp.cjs.meta.Because PnP uses a global cache and a mapping file, CI restores are typically faster and more reliable than copying thousands of small files in a node_modules directory.
To validate the migration on Node.js 20, run these scoped checks:
# Verify native addon loading
node -e "require('your-native-package')"
# Check for undeclared dependencies (PnP will throw errors here)
yarn run test
Diagnostic Detail Needed: Do you use any legacy build tools (e.g., Webpack 4 or older Gulp plugins) that are not updated for Yarn 4? This determines if you will need the pnpify wrapper for your build pipeline.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.