Why Yarn Plug’n’Play is a Game‑Changer for Monorepos
Yarn Plug’n’Play replaces node_modules with a .pnp.js file, cutting install time and disk usage. Learn how to enable, verify, and handle legacy tooling in a monorepo setup.
06 Dec 2025, 10:24 UTC

Problem: Duplicate Dependencies in Monorepos
In a typical monorepo with multiple packages, you often end up with the same library installed in node_modules for each package. This not only bloats disk usage but also slows down yarn install and can lead to subtle version conflicts.
Thesis: Yarn’s Plug’n’Play (PnP) Eliminates the node_modules Folder
Plug’n’Play stores all dependency metadata in a single .pnp.js file and resolves modules through a virtual file system. The result is a faster install, smaller disk footprint, and a single source of truth for dependency versions.
1. What is Plug’n’Play?
Plug’n’Play replaces the traditional node_modules layout with a lightweight JavaScript file that maps module names to their exact file paths. When Node loads a module, Yarn’s runtime intercepts the request and consults .pnp.js instead of the file system.
Key Concepts
- Virtual File System (VFS): A logical representation of where packages live, hiding the physical folder structure.
- Package Metadata: Information about each dependency, including its location, version, and exported entry points.
- Compatibility Shims: Optional wrappers that make legacy tools expecting a
node_modulesfolder work with PnP.
2. How Yarn Implements PnP
When nodeLinker is set to pnp, Yarn performs the following during yarn install:
- Resolves dependency graph and deduplicates versions.
- Writes a
.pnp.jsfile containing a mapping of module names to their physical locations. - Optionally rewrites imports for packages that expose non‑standard entry points (e.g.,
mainvsmodule).
This process removes the need for a node_modules directory entirely.
3. Enabling and Verifying PnP
Below is a step‑by‑step guide you can copy‑paste into your terminal.
# 1. Ensure you’re on Yarn 2+ (classic Yarn 1.x does not support PnP).
# 2. In the project root, enable PnP:
# yarn config set nodeLinker pnp
# 3. Install dependencies:
yarn install
# 4. Verify that node_modules is absent and .pnp.js exists:
# (Linux/macOS)
ls -la
# You should see .pnp.js but no node_modules folder.
# 5. Run a simple script to test resolution:
# Create a file src/test.js with the following content:
# import _ from "lodash";
# console.log(_.join(["Hello", "world"], " "));
# Execute it with the PnP loader:
node -r .pnp.js src/test.js
# Expected output: "Hello world"
**Permissions**: The commands must be run in the project root directory where you have write access. No elevated privileges are required.
4. Compatibility & Trade‑offs
While PnP offers many benefits, some legacy tooling may not be PnP‑aware. Common culprits include:
- Babel presets that check
node_modulesfor polyfills. - Test runners that rely on
require.resolvepaths. - Build tools that perform
fs.existsSyncchecks againstnode_modules.
To address these, Yarn provides a pnpify command that creates a temporary shim:
yarn pnpify --node-modules babel-cli
# This rewrites the package to use a fake node_modules layout for the duration of the command.
**Trade‑off**: Enabling PnP can increase startup time for some scripts that perform many module resolutions, as each lookup goes through the JavaScript resolver instead of a simple file system lookup. In practice, this is negligible for most workloads but worth noting for very large projects.
Concrete Example: Two Packages Sharing Lodash
Consider a monorepo with two packages, pkg-a and pkg-b, both depending on lodash@4.17.21. With classic node_modules, each package would have its own copy of lodash, duplicating ~1 MB of code.
# Root package.json
{
"private": true,
"workspaces": ["pkg-a", "pkg-b"],
"dependencies": {
"lodash": "^4.17.21"
}
}
# pkg-a/package.json and pkg-b/package.json both list "lodash" in dependencies.
After running yarn install with PnP enabled:
- No
node_modulesfolder appears underpkg-aorpkg-b. - A single
.pnp.jsfile resides at the root, referencing the sharedlodashpackage. - Both packages can import
lodashwithout any path changes.
To confirm, run a script from each package that imports lodash and logs a value. Both should work identically.
Actionable Closing
If you’re working in a monorepo or simply want faster installs, switch to Yarn PnP:
- Set the linker:
yarn config set nodeLinker pnp - Re‑install:
yarn install - Run tests/builds with
node -r .pnp.jsoryarn pnpifyas needed. - Monitor for errors: If a tool fails with “cannot find module”, add a
pnpifyshim or update the tool to a PnP‑aware version.
Once you confirm everything works, you’ll enjoy a leaner repository, faster CI pipelines, and a single source of truth for dependency versions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.