npm — Yarn & pnpm
While npm is the default package manager, Yarn and pnpm offer compelling alternatives with different architectural philosophies. Each addresses specific shortcomings of npm — Yarn focuses on reliability and speed, pnpm on disk efficiency and strict dependency isolation.
Choosing the right package manager depends on your team's workflow, project size, monorepo requirements, and CI/CD constraints. This guide covers the strengths, trade-offs, and migration paths for each option.
Yarn Berry is a complete rewrite of Yarn Classic. Its defining feature is Plug'n'Play (PnP), which eliminates node_modules entirely by generating a single dependency map. This dramatically reduces install time and disk usage.
| 1 | # Install Yarn Berry globally |
| 2 | npm install -g yarn |
| 3 | |
| 4 | # Enable Berry in a project |
| 5 | yarn set version berry |
| 6 | |
| 7 | # Install with PnP |
| 8 | yarn install |
| 9 | |
| 10 | # Classic node_modules mode (if PnP is incompatible) |
| 11 | yarn config set nodeLinker node-modules |
| 12 | |
| 13 | # Add dependencies |
| 14 | yarn add express |
| 15 | yarn add -D typescript |
| Feature | Yarn Berry | Benefit |
|---|---|---|
| PnP | No node_modules | 10x faster installs, smaller repos |
| Zero-Install | Caches in repo | No install step in CI |
| Constraints | JS-based rules | Enforce workspace policies |
| SDKs | Editor support | TypeScript works without hoisting |
warning
pnpm uses a content-addressable store where every version of every package is stored exactly once on disk. node_modules uses hard links and symlinks, making it both disk-efficient and strictly isolated — packages cannot access dependencies they did not declare.
| 1 | # Install pnpm globally |
| 2 | npm install -g pnpm |
| 3 | |
| 4 | # Initialize project |
| 5 | pnpm init |
| 6 | |
| 7 | # Install dependencies |
| 8 | pnpm add express |
| 9 | pnpm add -D typescript |
| 10 | |
| 11 | # Deterministic install |
| 12 | pnpm install --frozen-lockfile |
| 13 | |
| 14 | # List store usage |
| 15 | pnpm store status |
| 16 | |
| 17 | # Prune unused store packages |
| 18 | pnpm store prune |
| Feature | pnpm | Benefit |
|---|---|---|
| Store | Global content-addressable | Single copy of each package version |
| Isolation | Strict node_modules | Prevents undeclared dependency access |
| Speed | Fastest installs | Up to 2x faster than npm |
| Disk | Minimal usage | Savings of 40-70% over npm |
pro tip
Real-world benchmarks show significant differences in install speed, disk usage, and CI pipeline performance across the three package managers.
| Metric | npm | Yarn Berry (PnP) | pnpm |
|---|---|---|---|
| Clean install | ~45s | ~8s | ~12s |
| Cached install | ~12s | ~1s | ~3s |
| Disk usage (100 deps) | ~350MB | ~120MB | ~80MB |
| Lockfile size | ~500KB | ~200KB | ~180KB |
Benchmarks from a typical Next.js project with ~1000 dependencies. Actual results vary by project size, network speed, and package composition. pnpm and Yarn Berry consistently outperform npm in both cold and warm cache scenarios.
info
Both Yarn and pnpm can import existing package-lock.json files and generate their own lockfile formats. Migration is straightforward for most projects.
| 1 | # Migrate from npm to Yarn Berry |
| 2 | npm install -g yarn |
| 3 | cd my-project |
| 4 | yarn set version berry |
| 5 | yarn install |
| 6 | |
| 7 | # Migrate from npm to pnpm |
| 8 | npm install -g pnpm |
| 9 | cd my-project |
| 10 | pnpm import # converts package-lock.json to pnpm-lock.yaml |
| 11 | pnpm install |
| 12 | |
| 13 | # Verify migration |
| 14 | pnpm ls --depth=0 |
Common Migration Issues
npm shrinkwrap files are not compatible. Delete them before migrating. Some tools like patch-packagerequire additional configuration with pnpm. ESLint plugins may need explicit dependency declarations with pnpm's strict resolver.
| 1 | # Enable hoisting for specific packages (pnpm) |
| 2 | echo 'hoist-pattern[]=*eslint*' >> .npmrc |
| 3 | |
| 4 | # Use pnpm overrides to replace dependencies |
| 5 | pnpm override lodash@^4.0.0 lodash@4.17.21 |
best practice
Zero-Install is a Yarn Berry feature where the package cache (compressed zip archives) is committed to the repository. Developers and CI environments can skip the yarn install step entirely, as dependencies are immediately available after git clone.
| 1 | # Enable zero-installs in .yarnrc.yml |
| 2 | echo 'enableGlobalCache: false' >> .yarnrc.yml |
| 3 | |
| 4 | # The .yarn/cache directory contains zip files |
| 5 | # .pnp.cjs is the dependency map |
| 6 | # Both are committed to git |
| 7 | |
| 8 | # No install step needed after clone |
| 9 | git clone my-project |
| 10 | cd my-project |
| 11 | node index.js # works immediately |
| 12 | |
| 13 | # Update dependencies |
| 14 | yarn add lodash |
| 15 | git add .yarn/cache/ .pnp.cjs |
| 16 | git commit -m "chore: add lodash" |
warning
All three package managers support workspaces for monorepo management, but their approaches differ significantly. pnpm and Yarn Berry offer more advanced features for large-scale monorepos.
| Feature | npm Workspaces | Yarn Workspaces | pnpm Workspaces |
|---|---|---|---|
| Hoisting | Flat | Controlled | Strict (isolated) |
| Filtering | Basic | Constraints | Recursive filtering |
| Deduplication | Automatic | Manual (yarn dedupe) | Automatic |
| Maturity | Stable | Mature | Mature |
| 1 | # pnpm workspace config (pnpm-workspace.yaml) |
| 2 | packages: |
| 3 | - "packages/*" |
| 4 | - "apps/*" |
| 5 | - "!packages/deprecated" |
| 1 | # pnpm: run command across packages |
| 2 | pnpm --filter @my/app build |
| 3 | pnpm --filter "./packages/**" test |
| 4 | |
| 5 | # Yarn: run across workspaces |
| 6 | yarn workspaces foreach run build |
| 7 | yarn workspace @my/app add lodash |
| 8 | |
| 9 | # npm: scoped workspace commands |
| 10 | npm run build -w @my/app |
| 11 | npm run test --workspaces --if-present |
best practice