Yarn Plug’n’Play: The Practical Fix for Monorepo bloat
Yarn Plug’n’Play replaces the node_modules tree with a lightweight virtual filesystem, eliminating duplication and speeding installs in monorepos. Learn how it works, see a concrete example, and understand the tooling trade‑offs.
09 Dec 2025, 05:14 UTC

Problem: The node_modules Jungle
In a monorepo, each package declares its own dependencies and devDependencies. When yarn install runs, it flattens those lists into a single node_modules tree. With dozens of packages, the tree grows to hundreds of megabytes, often duplicating the same library in multiple places. Install times balloon, disk usage spikes, and version conflicts become a nightmare to track.
Thesis: Plug’n’Play (PnP) eliminates the tree
Yarn’s Plug’n’Play replaces the physical node_modules directory with a lightweight virtual file system. All dependency information lives in a single .pnp.js file. When require() or import is executed, Node’s module loader consults this graph instead of the file system. The result: no duplicated binaries, faster installs, deterministic resolution, and a single source of truth.
How PnP Works
- Graph construction: During
yarn install, Yarn walks the dependency tree and writes a dependency graph to.pnp.js. The graph maps package names and versions to the exact archive that satisfies the request. - Runtime patching: Yarn injects a small shim into Node’s loader (via
require("module")._resolveFilename) that intercepts module resolution. If the request matches an entry in the graph, the shim returns the path to the packed module in./.yarn/cache. - No physical files: Because the resolver knows where each module lives, there is no need to copy files into
node_modules. The cache directory holds tarballs, and the shim extracts them on demand.
Worked Example: A Two‑Package Repo
Assume a workspace with packages/app and packages/lib. app depends on lib and lodash.
# .yarnrc.yml
pnpMode: strict
# packages/app/package.json
{
"name": "app",
"private": true,
"dependencies": {
"lib": "^1.0.0",
"lodash": "^4.17.21"
}
}
# packages/lib/package.json
{
"name": "lib",
"version": "1.0.0",
"main": "index.js"
}
Run yarn install. After the install, verify:
ls -al packages/app/node_modules– should be empty or not exist.node -e "require('lodash')"– should output the lodash function.node -e "require('lib')"– should loadpackages/lib/index.jsvia the PnP resolver.
All modules resolve correctly without a physical node_modules tree.
Trade‑off: Tooling and Debugging Complexity
While PnP removes duplication, it introduces friction for tools that expect a real file system layout:
- Linters & IDEs: ESLint, TypeScript, VS Code may flag missing modules unless they are PnP‑aware. Adding
"nodeLinker": "pnp"to.yarnrc.ymland installing theeslint-plugin-pnportypescript-plugin-pnphelps. - Native addons: Packages that compile C/C++ code (e.g.,
node-gyp) often fail because they look for headers innode_modules. Workarounds includenode-gyp rebuild --pnpor patching the addon’sbinding.gyp. - Debugging: Stack traces may refer to virtual paths that look like
/app/node_modules/.pnp/which can be confusing for developers unfamiliar with PnP. - Large graphs: In very large workspaces, the
.pnp.jsfile can become large, potentially slowing resolution. Yarn 4+ mitigates this with on‑demand resolution.
Actionable Next Steps
- Enable PnP in your workspace: add
pnpMode: strictto.yarnrc.ymland runyarn install. - Update tooling: install PnP plugins for ESLint, TypeScript, and your IDE. For VS Code, install the "Yarn PnP" extension.
- Test native addons: run
node-gyp rebuildinside the package. If it fails, consider addingnode-gyp rebuild --pnpor using theyarn config set nodeLinker node-modulesfallback for that package. - Verify determinism: run
npx yarn why lodashto see that the dependency graph is used. - Monitor install times: compare
yarn install --silentbefore and after PnP to quantify the speedup.
By following these steps, you can reduce disk usage from 200 MB to under 20 MB, cut install times by 40‑60%, and gain a single, deterministic dependency graph.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.