ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 68

  1. Blog

pnpm vs npm vs Bun: Which Installs Fastest? 14s, 3.1s, 1.5s

npm 12.2.0 vs pnpm 12.9.1 vs Bun 1.4.2 on one 24-dependency React + Vite app: from scratch 14.2 s / 3.1 s / 1.5 s; lockfile + warm cache 2.9 s / 0.28 s / 0.27 s; lockfile + empty cache 3.9 s / 2.3 s / 0.45 s. Disk, lockfile size and the pnpm phantom-dependency catch.

Aditya Challa·5 October 2026·7 min read

Summary
On this page
  1. What I tested
  2. Which package manager installs fastest?
  3. How much disk does each one use?
  4. What breaks when you switch?
  5. Should you switch from npm to pnpm or Bun?
  6. Sources
  7. Related

On a from-scratch install of the same 24-dependency React + Vite app, npm 12.2.0 took a median 14.2 s, pnpm 12.9.1 took 3.1 s and Bun 1.4.2 took 1.5 s. With a lockfile and a warm cache the gap got wider: 2.9 s for npm ci against 0.28 s for pnpm and 0.27 s for Bun.

Short answer: if install time shows up in your CI bill or your daily loop, pnpm is the safe switch and Bun is the fastest. If you install a few times a day on one small repo, npm with a committed lockfile is fine. No affiliate links in this post.

What I tested

  • Machine: 8 vCPU Intel Xeon / 15 GB RAM Linux cloud box in a US data center, Node 24.21.0, tested 5 Oct 2026 (about 22:20 to 22:30 IST). The box is shared with other jobs, so I interleaved the three tools in every round instead of running each one back to back.
  • Versions: npm 12.2.0, pnpm 12.9.1 (the native binary), Bun 1.4.2. All three were the latest stable on npm that evening.
  • Project: one package.json with 10 dependencies (React 19, React Router 7, TanStack Query 5, Zod 4, Zustand 5 and others) and 14 dev dependencies (Vite 8, Vitest 5, TypeScript 6, ESLint 10, typescript-eslint, Prettier, Tailwind CSS 4, Playwright, jsdom 30). That resolved to 236 packages under npm, 235 under pnpm and 239 under Bun.
  • Each tool got its own empty cache directory, so nothing was shared between them.
  • Four scenarios, timed with Python's perf_counter around the whole command: from scratch (no lockfile, no cache), lockfile plus warm cache, lockfile with an empty cache, and a no-op re-install.
  • Runs: 6 from-scratch runs per tool, 3 runs for every other scenario. I report the median.
  • One surprise: Bun's node_modules came out 19% bigger than npm's (255 MB vs 215 MB). It installed both the glibc and the musl native binaries for Rolldown and Tailwind's oxide engine, where npm and pnpm installed only the glibc one.
  • Not tested: Windows or macOS, a monorepo with workspaces, Yarn, a home internet connection, packages that need install scripts.

Which package manager installs fastest?

Scenarionpm 12.2.0pnpm 12.9.1Bun 1.4.2
From scratch: no lockfile, empty cache14.2 s (13.6 to 16.0)3.1 s (2.8 to 8.0)1.5 s (1.4 to 3.4)
Lockfile, warm cache, no node_modules2.9 s (npm ci)0.28 s0.27 s
Lockfile, empty cache (CI with no cache)3.9 s (npm ci)2.3 s0.45 s
Re-install with nothing to do0.44 s0.04 s0.02 s

From scratch, pnpm was about 4.5x faster than npm and Bun about 9.7x faster. Both pnpm and Bun had one slow outlier each (8.0 s and 3.4 s). I kept those in the ranges because a slow registry response is part of real life, but the medians did not move much.

The most useful line for npm users is the gap between rows 1 and 3. With an empty cache but a committed package-lock.json, npm finished in 3.9 s instead of 14.2 s. Most of npm's from-scratch time went into resolving versions, not downloading. If your CI runs npm install without a lockfile, fixing that is worth more than switching tools.

A caution on network-bound rows: this box pulled a 4.5 MB TypeScript tarball from the npm registry in about 0.04 s. On a home connection rows 1 and 3 will be slower for every tool, and the gap between them may shrink.

How much disk does each one use?

MeasurenpmpnpmBun
node_modules (apparent size)215 MB204 MB255 MB
Cache or store after install433 MB520 MB (252 MB store + 268 MB metadata cache)364 MB
Lockfilepackage-lock.json 146.7 KBpnpm-lock.yaml 88.1 KBbun.lock 68.0 KB

pnpm's node_modules holds hard links into its store, so a second project with the same packages costs almost nothing extra on disk. That is the main reason teams with many repos or a monorepo pick it. For a single repo the three are close, apart from Bun's extra native binaries.

What breaks when you switch?

Everything I checked ran under all three installs: vite --version (8.3.2), tsc -v (6.0.3) and vitest --version (5.0.3). The one difference I hit was pnpm's strict layout. A require('@tailwindcss/oxide') from the app root worked under npm and Bun but failed under pnpm, because that package is a dependency of @tailwindcss/vite, not of my app. pnpm only exposes what you list in package.json.

That strictness is a feature, but it means code that imports packages it never declared (phantom dependencies) will fail after a switch. Fix it by adding the missing packages to package.json, not by turning hoisting back on everywhere.

Other things to check before you move:

  1. Commit only one lockfile. Delete package-lock.json when you adopt pnpm or Bun, or CI and teammates will disagree about versions.
  2. Pin the tool version. pnpm reads the packageManager field; set it so CI and laptops run the same 12.x binary. The pnpm 12.9.1 post covers the Rust rewrite and its split WASM package.
  3. Check your Node version. Bun installs packages without Node, but your app may still run on Node; the Node 26 LTS schedule post has the dates.
  4. If you use Bun only as an installer, keep an eye on its release notes; the Bun 1.4.2 post lists fixes to bun install itself.
  5. In a Turborepo or other monorepo, expect a cold remote cache after the switch, because a new lockfile changes task hashes; the Turborepo 2.11.7 post covers its cache changes.

Should you switch from npm to pnpm or Bun?

SituationMy pick
Small single repo, a few installs a dayStay on npm, but commit the lockfile and use npm ci in CI
CI runs dozens of installs a daypnpm: 2.9 s became 0.28 s with a warm cache here
Many repos or a monorepo on one machinepnpm, for the shared store
You want the fastest install and already use Bun elsewhereBun: 1.5 s from scratch, 0.45 s with an empty cache
Code imports packages it does not declareFix that first, or pnpm will fail on it

Bottom line: on my test app pnpm cut a warm-cache install from 2.9 s to 0.28 s and a from-scratch install from 14.2 s to 3.1 s, with the fewest surprises. Bun was faster still, at the cost of a larger node_modules. Who should not switch: a solo developer on one small repo, where the saving is a few seconds a day and the switch costs an afternoon of lockfile and CI changes.

How this was made: I ran every install above on the ShopperCove test box and kept the raw logs; the write-up was drafted with AI help and checked against those logs.

Sources

  • https://docs.npmjs.com/cli/commands/npm-ci
  • https://docs.npmjs.com/cli/commands/npm-install
  • https://github.com/npm/cli/releases
  • https://pnpm.io/cli/install
  • https://pnpm.io/motivation
  • https://github.com/pnpm/pnpm/releases
  • https://bun.com/docs/pm/cli/install
  • https://github.com/oven-sh/bun/releases

Related

  • https://www.shoppercove.com/blog/pnpm-12-9-1-rust-rewrite-wasm-split-october-2026
  • https://www.shoppercove.com/blog/bun-1-4-2-elysia-als-cmyk-jpeg-october-2026
  • https://www.shoppercove.com/blog/nodejs-26-lts-october-2026-schedule-change
  • https://www.shoppercove.com/blog/turborepo-2-11-7-sdkroot-oidc-cache-october-2026
  • https://www.shoppercove.com/blog/vite-plus-1-0-unified-toolchain-october-2026
  • https://www.shoppercove.com/blog/vite-8-3-2-renderbuilturl-bundled-dev-sourcemaps-october-2026
  • https://www.shoppercove.com/blog/vitest-5-should-you-upgrade-october-2026
  • https://www.shoppercove.com/blog/rspack-2-2-8-resolver-cache-memory-october-2026
npmpnpmbunpackage managerbenchmarksperformanceinstall speednode.js

Lab evidence

What I found running this

Hands-on on ShopperCove box 5 Oct 2026 ~22:20-22:30 IST (8 vCPU Intel Xeon / 15 GB shared Linux, US data center, Node 24.21.0). Tools: npm 12.2.0, pnpm 12.9.1 native binary, Bun 1.4.2 (npm latest that evening). Project: package.json with 10 deps (react 19, react-router 7, @tanstack/react-query 5, zod 4, date-fns 4, clsx, lucide-react, zustand 5, axios) + 14 devDeps (vite 8.3.2, @vitejs/plugin-react, typescript 6.0.3, vitest 5.0.3, jsdom 30.1.2, eslint 10.12.0, typescript-eslint 8.71.0, prettier 3.9.9, tailwindcss 4.3.3, @tailwindcss/vite, @playwright/test, @types/*, @testing-library/react). Separate empty caches per tool (npm --cache, pnpm --store-dir + XDG_CACHE_HOME, BUN_INSTALL_CACHE_DIR). Interleaved rounds, perf_counter wall time. From scratch (6 runs): npm 13.58/14.30/16.01/13.95/13.99/14.56 (median 14.15); pnpm 8.04/3.03/2.78/3.12/5.15/3.11 (3.12); bun 1.51/1.41/1.40/3.35/1.52/1.39 (1.46). Lockfile+warm cache (3): npm ci 3.06/2.85/2.89; pnpm frozen 0.29/0.28/0.28; bun frozen 0.28/0.27/0.27. Lockfile+empty cache: npm ci 4.02/3.85/3.68; pnpm 2.29/2.11/2.38; bun 0.45/0.37/0.49. No-op: 0.44/0.04/0.02 medians. Packages: npm added 236, pnpm +235, bun 239. node_modules apparent: 215/204/255 MB (bun installs gnu+musl bindings for @rolldown and @tailwindcss/oxide). Cache after: npm 433 MB, pnpm 520 MB (252 store + 268 metadata), bun 364 MB. Lockfiles 146,722 / 88,143 / 67,963 bytes. vite/tsc/vitest --version OK in all; require(@tailwindcss/oxide) from root fails only under pnpm (strict layout). Registry tarball 4.5 MB in ~0.04 s from box (fast link caveat). No affiliate.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 73

    volta vs fnm: Should You Switch? 14ms vs 17ms

    volta 2.0.2 vs fnm 1.39.0 switching Node 20.19.2 ↔ 22.14.0: session A-then-B median 13.9 ms vs 17.0 ms; steady node -v 7.4 ms vs 6.4 ms (direct 4.7 ms).

    5 Oct 2026

  • Plate 21

    c8 vs nyc: Should You Switch? 4.2s vs 7.4s

    c8 12.0.0 vs nyc 18.0.0 on 80 CommonJS modules / 480 mocha tests: coverage-run median 4222 ms vs 7415 ms (~1.76x; no-coverage baseline 1734 ms). Same code, different totals: branches 81.3% (c8) vs 47.9% (nyc).

    5 Oct 2026

  • Plate 21

    Vitest 5.0.3 vs 4.1.10: Should You Upgrade? (20-File Timing)

    Vitest 5 upgrade decision with ShopperCove timing: 20-file forks suite median ~512 ms on 5.0.3 vs ~547 ms on 4.1.10 (Node 22.20.0). Engines require Node 22.12+ and Vite 6.4+. clearMocks defaults true. No affiliate.

    5 Oct 2026

On this page

  1. What I tested
  2. Which package manager installs fastest?
  3. How much disk does each one use?
  4. What breaks when you switch?
  5. Should you switch from npm to pnpm or Bun?
  6. Sources
  7. Related
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove