Skip to content
Unverified — AI-generated content. Help verify this page

pnpm vs npm vs yarn vs bun

Package managers are the first tool you run on every project and the last thing you want to debug. Choosing the right one affects install speed, disk usage, CI pipeline duration, and monorepo workflow. This page compares the four most relevant JavaScript package managers across every dimension that matters.

Overview

pnpm

pnpm (performant npm) is a package manager created by Zoltan Kochan in 2017. Its core innovation is the content-addressable store — every version of every package is stored once on disk, and projects use hard links or symlinks to reference packages from the global store. This dramatically reduces disk usage and install times. pnpm uses a strict node_modules structure that prevents "phantom dependencies" (accessing packages you did not explicitly declare). pnpm has become the default package manager for major open-source projects including Vue, Vite, and SvelteKit.

npm

npm (Node Package Manager) is the original JavaScript package manager, created by Isaac Schlueter in 2010. It ships with Node.js, making it the default for every Node.js installation. npm v10+ has significantly improved performance, added workspace support, and improved security features. npm uses a flat node_modules structure (hoisting) and package-lock.json for deterministic installs. As the default package manager, npm has the largest user base and broadest compatibility.

yarn

yarn (Yet Another Resource Negotiator) was created by Facebook in 2016 to address npm's speed and reliability issues at the time. Yarn Classic (v1) introduced lockfiles and parallel downloads to JavaScript. Yarn Berry (v2-4) is a complete rewrite that introduces Plug'n'Play (PnP) — a radical approach that eliminates node_modules entirely, replacing it with a .pnp.cjs file that tells Node.js where packages are stored. Yarn Berry also supports zero-installs (committing your dependencies to git).

bun

bun is a JavaScript runtime and toolkit created by Jarred Sumner in 2022. Its built-in package manager is a byproduct of the runtime — it is written in Zig and designed for maximum speed. bun's package manager is npm-compatible (uses package.json, understands node_modules), installs packages faster than any other tool, and can run lifecycle scripts using bun's fast JavaScript engine. bun uses a binary lockfile (bun.lockb) for speed.

Architecture Comparison

Key Architectural Differences

pnpm stores every package version once in a global content-addressable store (~/.pnpm-store). When you install a package, pnpm creates hard links from node_modules/.pnpm to the store. Your node_modules/<package> is a symlink to the correct version in .pnpm. This means 10 projects using React 19 share a single copy on disk.

npm downloads each package into the project's node_modules directory. It uses a "flat" hoisting strategy — dependencies of dependencies are hoisted to the top level of node_modules to reduce duplication. This means you can accidentally import packages you did not declare in your package.json (phantom dependencies).

Yarn Berry (PnP) eliminates node_modules entirely. Packages are stored as compressed zip archives in .yarn/cache, and a .pnp.cjs file tells Node.js exactly where to find each package. This is the most radical approach — it eliminates filesystem traversal overhead but requires editor/tooling support and can break packages that hardcode node_modules paths.

bun uses a flat node_modules layout similar to npm but installs packages using a Zig-native HTTP client and file extractor, bypassing Node.js entirely. This makes it the fastest installer by a significant margin. The binary lockfile (bun.lockb) is faster to read/write than text-based lockfiles.

Feature Matrix

Featurepnpm 9npm 10Yarn Berry 4bun 1.x
Install strategyContent-addressable store + symlinksFlat hoistingPnP (no node_modules) or node_modulesFlat hoisting
Lockfilepnpm-lock.yamlpackage-lock.jsonyarn.lockbun.lockb (binary)
Lockfile formatYAML (human-readable)JSON (human-readable)YAML (human-readable)Binary (not readable)
WorkspacesExcellentGoodExcellentGood
Phantom dependency preventionYes (strict by default)No (hoisting allows it)Yes (PnP enforces)No (hoisting allows it)
Peer dependenciesStrict by defaultAuto-installStrictAuto-install
Patch packagespnpm patchManual (patch-package)yarn patch (built-in)Manual (patch-package)
Overridespnpm.overridesoverrides fieldresolutions fieldoverrides field
Scriptspnpm runnpm runyarn runbun run
npx equivalentpnpm dlxnpxyarn dlxbunx
Publishingpnpm publishnpm publishyarn npm publishNo (use npm)
Corepack supportYesN/A (ships with Node)YesNo
Offline modeVia store cachenpm cacheZero-installs (commit cache)Via cache
Shell autocompletionYesYesYesYes
Node.js requiredYesYes (ships with it)YesNo (bun runtime)
Config file.npmrc.npmrc.yarnrc.ymlbunfig.toml
Registrynpm registrynpm registrynpm registrynpm registry
Side effects cacheYesNoYesNo

Code Comparison

Common Commands

Taskpnpmnpmyarnbun
Install allpnpm installnpm installyarn installbun install
Add packagepnpm add reactnpm install reactyarn add reactbun add react
Add dev deppnpm add -D vitestnpm install -D vitestyarn add -D vitestbun add -d vitest
Remove packagepnpm remove reactnpm uninstall reactyarn remove reactbun remove react
Update packagepnpm update reactnpm update reactyarn up reactbun update react
Run scriptpnpm run devnpm run devyarn devbun run dev
Execute binarypnpm dlx create-vitenpx create-viteyarn dlx create-vitebunx create-vite
Clean installpnpm install --frozen-lockfilenpm ciyarn install --immutablebun install --frozen-lockfile
List depspnpm listnpm listyarn infobun pm ls
Auditpnpm auditnpm audityarn npm auditNo built-in
Why installedpnpm why reactnpm explain reactyarn why reactNo built-in

Workspace Configuration

yaml
packages:
  - 'packages/*'
  - 'apps/*'
  - '!**/test/**'
json
{
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
json
{
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
json
{
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

Workspace Commands

Taskpnpmnpmyarnbun
Run in specific packagepnpm -F @app/web devnpm -w @app/web run devyarn workspace @app/web devbun --filter @app/web dev
Run in all packagespnpm -r run buildnpm -ws run buildyarn workspaces foreach run buildbun --filter '*' build
Add dep to packagepnpm -F @app/web add reactnpm -w @app/web i reactyarn workspace @app/web add reactbun --filter @app/web add react
Link workspace depsAutomaticAutomaticAutomaticAutomatic

Monorepo Filtering

bash
# Run build only in packages that changed since main
pnpm -r --filter "...[origin/main]" run build

# Run tests in a package and all its dependencies
pnpm -F "@app/web..." run test

# Run in all packages except one
pnpm -r --filter "!@app/docs" run lint
bash
# Run in specific workspaces
npm -w @app/web -w @app/api run build

# Run in all workspaces
npm -ws run build

# No advanced filtering (use turbo or nx for this)
bash
# Run build in all workspaces in topological order
yarn workspaces foreach -Apt run build

# Run in packages matching pattern
yarn workspaces foreach -R --from "@app/web" run build

pnpm's filtering advantage

pnpm has the most powerful workspace filtering. You can filter by git diff (...[origin/main]), by dependency graph (@app/web... = package and its dependencies, ...@app/web = package and its dependents), and by exclusion. npm's workspace support is basic — for advanced monorepo orchestration with npm, pair it with Turborepo or Nx.

Performance

Install Speed (fresh install, no cache)

Project Sizepnpmnpmyarn (PnP)bun
Small (20 deps)3.5s6s5s1.5s
Medium (100 deps)8s18s12s3s
Large (500 deps)18s45s25s6s
Huge (1500+ deps)35s90s50s12s

Install Speed (warm cache / lockfile present)

Project Sizepnpmnpm (ci)yarn (PnP)bun
Small (20 deps)1.5s3s0.5s (zero-install)0.8s
Medium (100 deps)3s8s0.5s (zero-install)1.5s
Large (500 deps)7s20s0.5s (zero-install)3s
Huge (1500+ deps)15s50s0.5s (zero-install)6s

Yarn zero-installs

Yarn Berry's zero-install mode (committing .yarn/cache to git) means CI machines skip the install step entirely. The .pnp.cjs file resolves everything from the committed cache. This is the fastest possible CI install — effectively 0 seconds. The tradeoff is a larger git repository.

Disk Usage

Scenariopnpmnpmyarn (PnP)bun
1 project, 100 deps80 MB180 MB60 MB (zip cache)175 MB
10 projects, same deps85 MB (shared store)1.8 GB600 MB1.75 GB
10 projects, varied deps200 MB (shared store)2.5 GB800 MB2.4 GB

pnpm's disk advantage compounds

The more projects you have, the more pnpm saves. If you work on 10 projects that share common dependencies (React, TypeScript, ESLint), pnpm stores each package version once. npm and bun duplicate packages across every project's node_modules.

CI Pipeline Impact

Metricpnpmnpmyarn (PnP, zero-install)bun
Install step (cached)5-10s15-30s0s (committed)3-8s
Cache size (CI)~200 MB (store)~400 MB (cache)N/A (committed)~350 MB
Lockfile diff readabilityGood (YAML)Good (JSON)Good (YAML)Bad (binary)
ReproducibilityExcellentGoodExcellentGood

Developer Experience

Setup and Learning Curve

Aspectpnpmnpmyarnbun
No setup neededCorepack / npm i -g pnpmShips with Node.jsCorepack / manual installInstall bun runtime
Time to productive5 min (same commands)0 min (already know it)15 min (PnP setup, editor config)5 min (same commands)
Config complexityLow (.npmrc)Low (.npmrc)Medium (.yarnrc.yml, PnP)Low (bunfig.toml)
CompatibilityHigh (most packages work)HighestMedium (PnP breaks some)High (most packages work)
Error messagesGoodGoodGoodGood
DocumentationExcellentGoodGoodGood

Pain Points

Issuepnpmnpmyarnbun
Phantom dependenciesFixed (strict mode)Common problemFixed (PnP)Common problem
Peer dep conflictsStrict (can be verbose)Auto-install (can hide issues)StrictAuto-install
PnP compatibilityN/AN/ASome packages breakN/A
Lockfile merge conflictsModerate (YAML)Moderate (JSON)Moderate (YAML)Rare (binary, auto-resolve)
Binary lockfileNoNoNoYes (cannot review in PR)
Windows supportGoodExcellentGoodGood (improving)

Ecosystem Compatibility

Toolpnpmnpmyarnbun
TurborepoExcellentExcellentGoodGood
NxExcellentExcellentGoodGood
ChangesetsExcellentExcellentGoodGood
DockerGoodExcellentGoodGood
GitHub Actionssetup-pnpm actionBuilt-insetup-node corepacksetup-bun action
VercelSupportedDefaultSupportedSupported
NetlifySupportedDefaultSupportedSupported

When to Use Which

Decision Summary

ScenarioBest ChoiceWhy
Monorepo (any size)pnpmBest filtering, disk savings, strict deps
Open-source librarynpm or pnpmBroadest contributor compatibility
Maximum CI speedYarn Berry (zero-install)0-second install step
Fastest raw installbun3-5x faster than npm
Maximum compatibilitynpmShips with Node, everything supports it
Multiple projects (freelancer)pnpmContent store saves gigabytes of disk
Enterprise, conservativenpm or pnpmProven, well-understood
Bun-based projectbunNative integration with bun runtime
Need to review lockfilepnpm or npmHuman-readable lockfiles
Strict dependency enforcementpnpm or Yarn PnPPrevents phantom dependencies

Migration

npm to pnpm

  1. Install: npm install -g pnpm or enable via Corepack (corepack enable pnpm)
  2. Import lockfile: pnpm import converts package-lock.json to pnpm-lock.yaml
  3. Install: pnpm install creates the content-addressable node_modules
  4. Fix phantom deps: Add any missing dependencies that were previously hoisted
  5. Update scripts: Replace npm run with pnpm run (or just pnpm <script>)
  6. Update CI: Replace npm ci with pnpm install --frozen-lockfile
  7. Clean up: Delete package-lock.json
json
// package.json — add engines to enforce pnpm
{
  "packageManager": "pnpm@9.15.0",
  "engines": {
    "pnpm": ">=9"
  }
}

Phantom dependency errors

When switching from npm to pnpm, you may get import errors for packages you use but did not declare in package.json. npm's hoisting made these accessible accidentally. pnpm's strict node_modules structure exposes them. Fix by adding the missing packages as explicit dependencies.

npm/pnpm to bun

  1. Install bun: Follow bun.sh installation guide
  2. Install deps: bun install reads existing package.json and creates bun.lockb
  3. Update scripts: Replace npm run / pnpm run with bun run
  4. Update CI: Use setup-bun GitHub Action
  5. Test lifecycle scripts: Some postinstall scripts may behave differently

npm to Yarn Berry

  1. Install: corepack enable yarn and yarn set version stable
  2. Configure: Create .yarnrc.yml with nodeLinker: pnp (or node-modules for compatibility)
  3. Install: yarn install generates yarn.lock and .pnp.cjs
  4. Editor setup: yarn dlx @yarnpkg/sdks vscode for TypeScript support
  5. Fix PnP issues: Some packages need packageExtensions in .yarnrc.yml
  6. Optional zero-installs: Add .yarn/cache to git

Start with node-modules linker

If migrating to Yarn Berry, start with nodeLinker: node-modules in .yarnrc.yml. This gives you Yarn Berry's features without PnP compatibility issues. Once stable, you can switch to PnP for maximum performance.

Verdict

Choose pnpm for the best all-around package manager in 2026. Its content-addressable store saves significant disk space across projects, its strict node_modules prevents phantom dependencies, and its workspace filtering is the most powerful for monorepos. pnpm is the default for most major open-source projects (Vue, Vite, SvelteKit) and works with every CI system and deployment platform. It is the safe, modern default.

Choose npm if you want maximum compatibility and zero setup. npm ships with Node.js, so every developer has it immediately. For simple projects, open-source libraries that need contributor-friendly setup, and teams that do not want to learn a new tool, npm v10 is perfectly adequate. It is slower and uses more disk space, but those tradeoffs matter less for small projects.

Choose Yarn Berry if CI install speed is your top priority and your team is willing to invest in PnP setup. Zero-installs (committing dependencies to git) eliminates the install step in CI entirely, which is transformative for large projects with slow CI pipelines. The tradeoff is PnP compatibility issues and additional editor configuration.

Choose bun if you are already using bun as your JavaScript runtime and want the fastest possible install times. bun's package manager is 3-5x faster than npm for raw installation. The tradeoff is a binary lockfile (no human-readable diffs in PRs), fewer audit/security features, and a smaller community for troubleshooting.

Which Would You Choose?

Scenario 1: You maintain an open-source library on npm. Contributors should be able to clone the repo and start working with zero friction, regardless of which package manager they prefer.

Recommendation: npm

npm ships with Node.js, so every contributor has it by default. No installation step, no Corepack configuration, no "please install pnpm first" in your README. For open-source projects where contributor accessibility matters most, npm is the lowest-friction choice.

Scenario 2: You work as a freelancer on 15 different client projects. Your laptop's SSD is 256 GB, and node_modules across all projects is eating 8 GB of disk space.

Recommendation: pnpm

pnpm's content-addressable store means 15 projects sharing React, TypeScript, and ESLint only store each package version once on disk. You will reclaim 5-6 GB immediately. The more projects you have, the more pnpm saves.

Scenario 3: Your CI pipeline spends 90 seconds on npm install for every PR build. You have 50 PRs per day. Developers are frustrated waiting for CI.

Recommendation: Yarn Berry with zero-installs (or bun)

Yarn Berry's zero-install mode commits dependencies to git, making the CI install step literally 0 seconds. Alternatively, bun installs packages 3-5x faster than npm, cutting your 90-second install to ~20 seconds. Either approach saves your team hours per day.

Common Misconceptions

  • "pnpm's symlinks break things" — pnpm's strict node_modules structure exposes phantom dependencies (packages you use but did not declare). This feels like breakage but is actually pnpm fixing a real bug in your dependency declarations. Add the missing packages and your project is more correct.
  • "Yarn PnP breaks everything" — Yarn PnP breaks packages that hardcode node_modules paths, which is a diminishing minority. Most modern packages work fine. Start with nodeLinker: node-modules and switch to PnP when ready.
  • "bun's binary lockfile is a dealbreaker" — Binary lockfiles cannot be reviewed in PR diffs, which is a legitimate concern for security-conscious teams. However, bun automatically resolves merge conflicts (no manual lockfile merging), which is a genuine advantage.
  • "npm is too slow for real projects" — npm v10 with npm ci and a warm cache is acceptable for most projects. The speed difference matters most in CI pipelines and monorepos, not for typical development workflows.

Real Migration Stories

Vue/Vite ecosystem: npm to pnpm — The Vue, Vite, and Vitest projects all migrated to pnpm for their monorepo workspace management. pnpm's filtering (--filter "...[origin/main]") and strict dependency resolution caught phantom dependencies that npm's hoisting had hidden for years.

Vercel: yarn v1 to pnpm — Vercel migrated their internal monorepo from Yarn Classic to pnpm, citing better workspace filtering, strict dependency resolution, and faster install times. The migration also improved CI cache efficiency because pnpm's lockfile is more stable across updates.

Quiz

1. What is a "phantom dependency," and which package managers prevent it?

A phantom dependency is a package your code imports that is not declared in your package.json — it works because npm/yarn hoists it to the top of node_modules. pnpm and Yarn PnP prevent this by enforcing strict module resolution where only declared dependencies are accessible.

2. How does pnpm's content-addressable store save disk space?

pnpm stores every package version once in a global store (~/.pnpm-store). Projects use hard links to reference packages from this store. If 10 projects use React 19, only one copy exists on disk, linked 10 times.

3. What is Yarn Berry's "zero-install" mode?

Zero-installs means committing the .yarn/cache directory (compressed package archives) and .pnp.cjs (resolution map) to git. CI machines skip the install step entirely because all dependencies are already in the repository. The tradeoff is a larger git repository.

4. Why is bun's lockfile binary instead of text-based?

Binary lockfiles are faster to read and write than text-based YAML/JSON. bun prioritizes raw speed in every operation. The tradeoff is that binary lockfiles cannot be human-reviewed in PRs.

5. When should you use Corepack instead of globally installing pnpm or yarn?

Corepack (built into Node.js 16+) manages package manager versions per-project via the packageManager field in package.json. Use it when different projects require different pnpm/yarn versions, ensuring every developer and CI machine uses the exact same version.

One-Liner Summary

pnpm saves disk space and enforces correct dependencies, npm is the universal zero-setup default, Yarn Berry eliminates CI install time with zero-installs, and bun is the fastest raw installer.

"What I cannot create, I do not understand." — Richard Feynman