ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 79

  1. Blog

pm-detector vs which-pm: 0.49 ms vs 0.043 ms

Aditya Challa·5 October 2026·6 min read

Hands-on
On this page
  1. What I tested
  2. How fast is package-manager-detector vs which-pm?
  3. What does each one cost to install and bundle?
  4. Do they behave the same?
  5. Which one should you pick?
  6. Sources
  7. Related

I timed package-manager-detector 1.9.0 detect against which-pm 4.0.0 on a package-lock fixture on this box: 0.49 ms vs 0.043 ms per call. which-pm returned null on that lockfile-only tree; package-manager-detector reported npm. Gzip for the bundled detect+agent+resolveCommand entry was 2,501 B versus which-pm's 18,635 B.

Short answer: pick package-manager-detector when you need lockfile / packageManager-field detection, agent metadata, and resolveCommand helpers. Pick which-pm when you only care what already installed into node_modules (or bun.lockb) and want a tiny sync-ish check. They answer different questions. No affiliate links in this post.

What I tested

  • Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 03:55 IST.
  • Versions: package-manager-detector 1.9.0, which-pm 4.0.0, which-pm-runs 2.0.0 (npm latest that day), Node 20.19.2, esbuild 0.28.2 node ESM minify + gzip -9.
  • Workload: seven fixtures under /workspace/bench-pm-detector-which-pm/fixtures/ (npm-lock, pnpm-lock, yarn-lock, empty, nm-only, with-pm-field, bun-lock). Timed detect({ cwd, stopDir }) vs whichPM(cwd), plus walking detect without stopDir, plus getUserAgent vs whichPMRuns with a fake npm_config_user_agent. Median of 9 × 200 async calls, two processes. Scripts and JSON in /workspace/bench-pm-detector-which-pm/.
  • Also measured: esbuild bundles (full pmd, detect-only, which-pm, which-pm-runs) and alone-on-disk install size.
  • Not tested: yarn Berry integrity files beyond .yarn-integrity, rush.json edge cases, Windows path walks, Bun/Deno hosts, and Node 22+ even though which-pm / which-pm-runs declare engines.node >= 22.13 (both still ran here on 20.19.2).

How fast is package-manager-detector vs which-pm?

Job (median of 9, ms, stopDir = fixture)pmd detectwhich-pm
package-lock.json present0.4930.043
pnpm-lock.yaml present0.4070.045
yarn.lock present0.4660.041
bun.lock + empty bun.lockb0.2360.043
empty package.json only0.6450.045
node_modules dir, no lock0.6520.044
packageManager field pnpm@9.12.00.5540.042

which-pm was about 11× faster on the package-lock fixture (0.043 vs 0.49 ms) because it only probes node_modules metadata and bun.lockb — it never opens the lockfile. On empty fixtures, detect without stopDir walked parents and took about 1.62 ms here; with stopDir it stayed about 0.65 ms and returned null. Process-agent checks were sub-microsecond: getUserAgent about 0.00045 ms vs whichPMRuns about 0.00081 ms with a set user agent. For spawning the resolved PM next, see tinyexec vs execa; for dead-dependency cleanup after installs, knip vs depcheck.

What does each one cost to install and bundle?

Package / entryMinifiedgzipInput filesAlone on disk
package-manager-detector (detect + getUserAgent + resolveCommand)8,196 B2,501 B584 KB (1 package)
pmd detect-only2,728 B1,114 B3—
which-pm56,056 B18,635 B91,400 KB (which-pm + load-yaml-file tree)
which-pm-runs260 B200 B228 KB

package-manager-detector is zero-dependency and about 7× smaller gzip than which-pm's bundled graph here. which-pm-runs is a separate tiny package for the process agent (closest sibling to getUserAgent). Size pressure in CI is the same theme as size-limit vs bundlesize; path joins while walking parents pair with pathe vs node:path.

Do they behave the same?

Fixture (stopDir = fixture)package-manager-detectorwhich-pm
package-lock.json{ name: npm, agent: npm }null
pnpm-lock.yaml{ name: pnpm, agent: pnpm }null
yarn.lock{ name: yarn, agent: yarn }null
empty package.jsonnullnull
node_modules onlynull{ name: npm }
packageManager: pnpm@9.12.0pnpm agent + version 9.12.0null
bun.lock + bun.lockb{ name: bun, agent: bun }{ name: bun }
npm_config_user_agent=pnpm/...getUserAgent → pnpmwhichPMRuns → { name: pnpm, version: 9.12.0 }
resolveCommand(pnpm, add, [lodash]){ command: pnpm, args: [add, lodash] }N/A
engines.nodeunset>= 22.13 (ran on 20.19.2)

The important gap: which-pm inspects installation metadata under node_modules (and bun.lockb), so a fresh repo with only a lockfile returns null until you install. package-manager-detector reads lockfiles and package.json fields and can walk parents unless you set stopDir. Agent lists: 11 agents and 12 lock names in pmd constants on this version. Logging around install scripts is covered in consola vs pino; globbing lockfiles in a monorepo pairs with fast-glob vs globby. For shipping a small detector CLI, tsup vs unbuild and picocolors vs chalk are the usual neighbors.

Which one should you pick?

SituationMy pick
Detect PM from lockfile / packageManager field before installpackage-manager-detector
Detect what already filled node_moduleswhich-pm
Need resolveCommand / constructCommand for add/runpackage-manager-detector
Only read npm_config_user_agentgetUserAgent or which-pm-runs
Smallest install + gzip for lockfile detectpackage-manager-detector
Hot path that only checks node_moduleswhich-pm (about 11× here)

Bottom line: on this box package-manager-detector 1.9.0 took about 0.49 ms to detect npm from package-lock.json while which-pm 4.0.0 finished in about 0.043 ms but returned null until node_modules existed. Gzip favored pmd at 2.5 KB against which-pm's 18.6 KB. Who should not switch to which-pm: scaffolders and CI that must choose a PM from the lockfile alone. Who should not pull pmd for a post-install check: scripts that only need to know what already landed in node_modules and want the cheaper probe.

How this was made: I installed the packages on the ShopperCove box, built seven fixtures, timed detect (with and without stopDir) vs whichPM over 200 calls × 9 rounds in two processes, compared getUserAgent to whichPMRuns, bundled each entry with esbuild, and recorded install sizes. The JSON results sit next to the scripts. The write-up was drafted with AI help and checked against that output.

Sources

  • https://github.com/antfu-collective/package-manager-detector
  • https://github.com/zkochan/packages/tree/main/which-pm
  • https://github.com/zkochan/packages/tree/main/which-pm-runs
  • https://www.npmjs.com/package/package-manager-detector
  • https://www.npmjs.com/package/which-pm
  • https://esbuild.github.io/

Related

  • https://www.shoppercove.com/blog/tinyexec-vs-execa
  • https://www.shoppercove.com/blog/knip-vs-depcheck
  • https://www.shoppercove.com/blog/size-limit-vs-bundlesize
  • https://www.shoppercove.com/blog/pathe-vs-node-path
  • https://www.shoppercove.com/blog/consola-vs-pino
  • https://www.shoppercove.com/blog/fast-glob-vs-globby
  • https://www.shoppercove.com/blog/tsup-vs-unbuild
  • https://www.shoppercove.com/blog/picocolors-vs-chalk
package-manager-detectorwhich-pmjavascriptnodejsnpmpnpmyarnperformance

Lab evidence

What I found running this

Hands-on on ShopperCove box 6 Oct 2026 ~03:55 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). package-manager-detector 1.9.0 vs which-pm 4.0.0 vs which-pm-runs 2.0.0; esbuild 0.28.2. Fixtures: npm/pnpm/yarn/bun locks, empty, nm-only, packageManager field. Median of 9 × 200 (ms, stopDir): pmd 0.493/0.407/0.466/0.236 vs which-pm ~0.043 across locks; empty 0.645 vs 0.045. Behavior: lockfile-only → pmd name+agent, which-pm null; nm-only → which-pm npm, pmd null with stopDir; getUserAgent pnpm vs whichPMRuns {name,version}. Bundle gzip 2,501 vs 18,635 B; install 84 vs 1,400 KB. engines.node >=22.13 on which-pm packages, ran on 20.19.2. Not tested: yarn Berry beyond integrity, Windows, Bun/Deno hosts. No affiliate.

Notes when a lab post goes up

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

Related links

  • Plate 68

    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.

    5 Oct 2026

  • Plate 71

    find-up vs locate-path: 47 µs Sync vs 596 µs Async

    5 Oct 2026

  • Plate 97

    c12 vs cosmiconfig: 0.58 ms vs 1.08 ms per .mjs Load

    5 Oct 2026

On this page

  1. What I tested
  2. How fast is package-manager-detector vs which-pm?
  3. What does each one cost to install and bundle?
  4. Do they behave the same?
  5. Which one should you pick?
  6. Sources
  7. Related
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove