Yarn Plug'n'Play: Zero‑Install Monorepos Without node_modules
Yarn’s Plug’n’Play (PnP) removes the traditional node_modules tree, replacing it with a single .pnp.cjs map. This article walks through how PnP works, how to migrate a monorepo, the trade‑offs for CI/CD and tooling, and a practical zero‑install setup you can try today.
05 Dec 2025, 08:34 UTC

The Problem: node_modules Bloat and Fragility
In a typical Node.js project, yarn install generates a node_modules directory that can contain thousands of files. In a large monorepo, this tree can grow to hundreds of megabytes, leading to:
- Long install times (often 30–60 s on CI).
- High disk usage and frequent cache invalidation.
- Fragile runtime errors that only surface on a developer’s machine.
Yarn’s Plug’n’Play (PnP) was designed to solve these problems by replacing the file‑system lookup with an in‑memory map.
Plug’n’Play Explained
When PnP is enabled, Yarn creates a single .pnp.cjs (or .pnp.mjs) file in the project root. This file exports a loader that Node consults instead of walking the node_modules tree. The loader contains a dependency graph that maps package names and versions to their physical locations in the .yarn/cache directory.
Key consequences:
- No
node_modulesdirectory. All modules live in.yarn/cacheand are referenced by the loader. - Fast installs. Yarn only needs to write the cache and the
.pnp.cjsfile, cutting install time by 50–70 % in typical monorepos. - Strict dependency boundaries. A package cannot require a dependency that is not declared in its
package.json. Missing deps surface at install time, not at runtime.
Practical Migration Steps
Below is a minimal, repeatable workflow for migrating an existing Yarn 2+ project to PnP and enabling zero‑install CI.
- Verify Yarn version
# Ensure you are on Yarn Berry (v3 or v4) $ yarn --version # Should output a number >= 3.0.0 - Enable PnP (default)
If your project already uses Yarn 2+, PnP is the default. To be explicit, add or update
.yarnrc.yml:nodeLinker: pnp - Generate the PnP loader
$ yarn install # Creates .pnp.cjs and .yarn/cache - Commit cache and loader for zero‑install
Add the following to
.gitignoreto keep only what is needed:.yarn/install-state.gz .yarn/unpluggedThen commit
.pnp.cjs,.yarn/cache, and any patches in.yarn/patches:$ git add .pnp.cjs .yarn/cache .yarn/patches $ git commit -m "Add PnP cache for zero‑install" - Editor integration (VS Code example)
Yarn provides SDKs that generate editor settings to make TypeScript and ESLint aware of PnP. Run:
$ yarn dlx @yarnpkg/sdks vscode # This writes .vscode/settings.json with a typescript.tsdk pointing to the SDK - CI zero‑install script
In your CI pipeline, you can skip network fetches entirely:
# Example GitHub Actions step - name: Install dependencies run: yarn install --immutable env: YARN_ENABLE_SCRIPTS: false # The --immutable flag ensures the cache is used as‑is. - Validate the loader
After the install, confirm that Node can resolve modules via PnP:
$ node -e "require('./.pnp.cjs'); console.log('PnP loaded');" # Expected output: PnP loaded
Trade‑offs & Compatibility
While PnP brings many benefits, it introduces a few practical considerations:
- Tooling assumptions. Any tool that expects a
node_modulesdirectory or walks the file system will fail. Common culprits include older Babel plugins, custom scripts, and some DockerfileCOPYpatterns. If you encounter such errors, switch thenodeLinkertonode-modulesfor that workspace or add a shim. - Binary packages. Native addons that run
postinstallscripts (e.g.,sharp,canvas) may write to the cache. Ensure your CI environment matches the target architecture; otherwise, use Docker or thesupportedArchitecturessetting. - Cache size. The
.yarn/cachedirectory can grow large (100 MB–1 GB+). If repository size is a concern, runyarn cache cleanbefore committing, but then you lose the offline benefit. - Node version. Yarn 4 requires Node ≥ 18.12 for full PnP support. Older Node releases may lack the loader integration.
- Mixed linkers. In a monorepo, all workspaces should share the same
nodeLinkersetting unless you have a specific need. Mixingpnpandnode-modulescan cause resolution mismatches.
Actionable Checklist
- Check Yarn version and
nodeLinkersetting. - Run
yarn installand commit.pnp.cjs,.yarn/cache, and patches. - Generate editor SDKs for TypeScript/ESLint.
- Update CI to use
yarn install --immutableand verify offline install. - Audit tooling for
node_modulesassumptions and adjust or shim. - Monitor cache size and prune if necessary.
By following this workflow, you can enjoy faster installs, tighter dependency enforcement, and hermetic CI builds—all without the overhead of a massive node_modules tree.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.