TL;DR
- Speed:
pnpmand modern Yarn (Berry) are usually fastest thanks to shared caches and smarter linking.npmhas improved a lot (v7+) but tends to be slower on cold installs. - Disk:
pnpmwins for large monorepos: a content‑addressable store + hard links → tinynode_modules. Yarn PnP can be smallest (nonode_modules), but needs ecosystem compatibility. - Lockfiles: Commit them.
npm→package-lock.json, Yarn →yarn.lock,pnpm→pnpm-lock.yaml. Use frozen/immutable installs in CI to guarantee determinism. - Choosing: Default to pnpm for big repos or when disk matters; Yarn (Berry) when you want PnP or high‑end workspace features; npm when you need zero extra tooling and broadest compatibility.
What actually differs (practical view)
1) Install speed
- Cold installs: pnpm/Yarn reuse a global store and link files, avoiding duplicate extraction; npm extracts per project.
- Re‑installs: pnpm/Yarn tend to be much faster because they link from cache;
npm ciis faster thannpm installfor CI. - Monorepos: pnpm and Yarn workspaces install once for the whole repo; npm workspaces exist but are newer/less optimized.
2) Disk usage / node_modules
- npm (hoisted layout): flattens dependencies; can produce huge
node_moduleswith duplicates across projects. - pnpm (symlinked layout): stores one copy of each package/version in a content‑addressable store and links into projects → dramatically smaller disk footprint.
- Yarn (Berry) Plug’n’Play (PnP): optionally skips
node_modulesentirely; a.pnp.cjsfile maps imports. Smallest on disk but some tools still expectnode_modules(you can switch Yarn tonodeLinker: node-modules).
3) Lockfile & determinism
- Files:
package-lock.json(npm),yarn.lock(Yarn),pnpm-lock.yaml(pnpm). Always commit these. - CI flags:
- npm:
npm ci(errors if lock andpackage.jsondisagree). - Yarn 1:
yarn install --frozen-lockfile - Yarn Berry (2+/3+):
yarn install --immutable - pnpm:
pnpm install --frozen-lockfile
- npm:
- Integrity: All verify package integrity via checksums; pnpm’s lockfile is YAML and workspace‑aware by default.
Quick compare (day‑to‑day)
| Topic | npm (v9+) | Yarn (1.x / Berry) | pnpm (8+) |
|---|---|---|---|
| Speed | Solid, slower on cold install | Yarn 1 fast; Berry very fast | Fastest in most mono repos |
| Disk use | Highest (duplicates) | PnP: smallest; node‑modules linker ≈ npm | Low via global store + links |
| node_modules | Classic hoisting | PnP (no node_modules) or node-modules | Symlinked, strict by design |
| Workspaces | ✅ (basic) | ✅ mature (Berry shines) | ✅ mature, great for mono repos |
| Deterministic CI | npm ci | --frozen-lockfile / --immutable | --frozen-lockfile |
| Strictness (phantom deps) | Permissive | PnP forbids; node‑modules looser | Forbids phantom deps |
| Setup friction | None (bundled) | Needs Corepack/config for Berry | Needs Corepack or separate install |
| Ecosystem compat | Maximum | Great; some tools need tweaks with PnP | Great; rare edge cases |
Phantom deps = importing a package you didn’t list in
package.jsonbut that happened to be hoisted. pnpm (and Yarn PnP) prevent this, helping you catch mistakes early.
Commands & flags you’ll actually use
Install & run
# npm
npm install # or: npm ci (for CI)
npm run build
# Yarn (Berry recommended via Corepack)
corepack enable
yarn install # or: yarn install --immutable
yarn build
# pnpm
corepack enable
pnpm install # or: pnpm i --frozen-lockfile
pnpm build
Add/remove deps
# npm
npm i lodash # --save-dev for dev deps
npm un lodash
# Yarn
yarn add lodash
yarn remove lodash
# pnpm
pnpm add lodash
pnpm remove lodash
Workspaces (monorepo root)
# npm (package.json: "workspaces": ["packages/*"])
npm -w packages/web add react
# Yarn (berry)
yarn workspace web add react
# pnpm (pnpm-workspace.yaml)
pnpm -F web add react
Deterministic CI install
npm ci
yarn install --immutable
pnpm install --frozen-lockfile
When to pick which
- pnpm: large monorepos, disk pressure, strict dependency hygiene, fast installs.
- Yarn (Berry): want PnP (no
node_modules), very polished workspace DX, and strong constraints. - npm: smallest moving parts, no extra tooling, or constrained environments where only npm is available.
Pitfalls & quick fixes
| Problem | Why it bites | Fix |
|---|---|---|
| Mixed lockfiles (yarn.lock + package-lock.json) | Nondeterministic installs | Pick one manager; delete the other lockfiles |
| “Works on my machine” via phantom deps | Hidden hoist behavior | Use pnpm or Yarn PnP; or run npm ls to detect |
| Tools break under Yarn PnP | They assume node_modules | Set nodeLinker: node-modules or use pnpm |
| CI slow with npm | Rebuilds from scratch | Use npm ci and a shared cache; consider pnpm |
| Huge node_modules | Disk waste, slow editors | Switch to pnpm or Yarn PnP |
| Global store growth (pnpm/yarn) | Cache bloat over time | Periodically pnpm store prune / yarn cache clean |
Quick checklist
- [ ] Commit the lockfile; fail CI when it changes unexpectedly.
- [ ] Use immutable/frozen installs in CI.
- [ ] For monorepos, enable workspaces and a shared cache.
- [ ] Guard against phantom deps (pnpm/Yarn PnP).
- [ ] Periodically prune caches and audit (
npm audit/pnpm audit/yarn npm audit).
One‑minute adoption plan
- Pick a manager and standardize (document + precommit check).
- Turn on workspaces and immutable/frozen CI installs.
- For big repos, trial pnpm on a branch; compare install time and disk use.
- If adopting Yarn Berry, decide between PnP vs
node-moduleslinker and update tooling. - Add cache pruning to housekeeping and watch metrics (CI time,
node_modulessize).