pnpm 12 Went Rust. The Upgrade Took Ten Minutes and One CI Fix
Last week I called Bun 1.4 the last domino in JavaScript tooling going native. That aged well. Three days later the pnpm team shipped version 12, and the whole install engine is Rust now. Roughly two thirds of the codebase switched languages overnight, and the funniest summary came from the maintainer himself: it was faster to rewrite pnpm in Rust than to migrate pnpm to ESM. I've migrated projects to ESM. I believe him.
This is part four of me dragging my toolchain through the native rewrite wave, after Vite 8 and Rolldown, the TypeScript 7 Go compiler, and Bun's Rust jump. Unlike those, this one barely qualifies as an upgrade. That's the interesting part.
The upgrade is one command, with one catch
Here's the whole migration:
pnpm self-update next-12
That's it. Commands, flags, settings, lockfile format, even the node_modules layout all carry over from pnpm 11. One warning though: the latest tag on npm still points to the 11 line, so npm i -g pnpm gets you the old build. Homebrew hadn't picked up 12 yet either when I checked. If you install through a system package manager, wait or use the standalone script.
The speed claims are real but read them carefully. The headline number is warm installs: 472ms down to 15ms when the store and lockfile are already there. Your first cold install is more like 8 seconds to 5 on a file-heavy project. Socket ran numbers on Vercel's Turborepo workspace, 1,670 packages across 21 projects, and got median install reductions between 64% and 90%. In my own monorepo the difference is honestly hard to feel on a single install. Where it shows up is CI, where you run installs hundreds of times a day and every warm cache hit compounds.
One tradeoff nobody mentions: the native binary is chunkier, so a cold first startup was about 11% slower in the release benchmarks. Cached startup is 75% faster. Net win, just not free.
Three things that can actually break your CI
The changelog calls these minor. Two of them would have broken my pipeline if I'd merged blindly.
First, git dependencies are now identities, not transports. github:owner/repo, git+ssh://, git+https:// all resolve through the host's canonical HTTPS URL, and the lockfile never records an SSH URL for GitHub, GitLab, or Bitbucket. Sounds harmless until you have private repos that authenticate over SSH in CI. pnpm shells out to git, so the fix is git's own URL rewriting, not a pnpm setting:
git config --global url."[email protected]:".insteadOf https://github.com/
Put that in your CI setup step if your deploy key is SSH based. Unknown hosts keep their exact URLs, so this only affects the big three.
Second, pnpm install --resolution-only is gone. It's pnpm peers check now. If your CI greps for resolution conflicts the old way, that step dies silently. Mine did.
Third, typos in pnpm-workspace.yaml no longer vanish quietly. I once spent an afternoon wondering why minimumReleaseAge wasn't slowing down installs. Answer: I'd misspelled it, and pnpm 11 ignored unknown keys without a word. Version 12 reports them, and if your project pins a pnpm version it fails the command outright. Annoying for one commit, then exactly the behavior you wanted all along.
The lockfile diff you shouldn't panic about
Peer resolution in dependency cycles used to depend on the order pnpm walked the graph, which meant the same repo could produce different lockfiles on different machines. Version 12 cuts cycles at a fixed place, so lockfiles are byte-identical no matter what. The cost: the first install that re-resolves rewrites those peer variants, and you'll see a one-time lockfile diff that looks scary and isn't. --frozen-lockfile still consumes old lockfiles unchanged, so nothing forces the rewrite until you actually resolve. Bonus for big monorepos: cycle-heavy workspaces resolve peers two to three times faster with about 25% less memory.
There's also a small feature I didn't expect to like. A globally installed node, deno, or bun now follows the version the current project pins. No shell hooks, no use command. I have four projects on four Node versions and one less thing to think about.
Worth it?
For a solo dev with a couple of repos, this upgrade is a nothing burger, in the best way. Run the command, glance at the git dependency thing, fix your peers check if you had it in CI. Ten minutes.
For teams with big monorepos and long CI queues, warm installs going from half a second to nearly nothing changes how often people bother pulling before pushing. That's a cultural effect, not a benchmark one, and it's the reason this rewrite matters more than the benchmarks suggest.
My full upgrade checklist for this one lives in Snippet Ark next to the Bun drill from last week. At the rate native rewrites are landing, I'm keeping a running list. Something in this ecosystem is written in JavaScript, and apparently that's a bug now.