配了八年 npm,今天才发现它的替代者早就在 Rust 里重生了一遍

pnpm 12 上周正式发布,核心数字有点反常识:同样一个项目,热安装(缓存+lockfile 都在)从 472ms 压到 15ms,约 30 倍;完整冷装(什么都没缓存)从 8.2s 压到 5s,约 1.6 倍;对比 npm 的 47.7s,差距是 9 倍。这个「30 倍」不是在说什么花哨场景——就是你每天在 CI 里跑的那条命令,每次 pull 之后本地验证依赖树的那一步,之前要等半秒,现在 15ms。

这不是换了一套算法,这是把整个安装引擎用 Rust 重写了。

pnpm 12 到底变了什么

pnpm 12 的核心改动代号 pacquet(法语「小包」),是 pnpm 官方用 Rust 对安装引擎的完整重写。11.2 开始通过 @pnpm/pacquet 接入,11.7 实现端到端 Rust 化,12 成为默认。

重写之后,Node.js 启动开销归零——pnpm 12 以原生二进制分发,不再需要先拉起一个 Node 运行时才能跑命令。对于一次只要几十毫秒的热安装,这半个 472ms 之前有相当一部分是在等 Node.js 启动。

文件 I/O 也从单线程 JS 线程转移到了 Rust 的原生并行。安装时要做的事情本质上是很吃 I/O 的:获取 tarball、解析依赖图、解压、硬链接到 node_modules。全部经由一个 JS 事件循环去完成,其实没有充分利用操作系统的并行能力。Rust 直接跑在系统调用层,多核用满。

还有一个更实在的数字:pnpm 2025 年在 monorepo 场景的市占率是 38%(State of JS 调查,10251 名开发者),是 npm 和 Yarn 加起来的两倍。这个体量的工具提速 30 倍,CI 受益最直接——每一次 workflow 触发都要重新验证依赖树,这一步现在只要 15ms。

升级真的不需要改任何东西

这是 pnpm 12 最反直觉的地方:变化很大,但体验几乎不动。

pnpm 创始人 Zoltan Kochan 明确说过:「pnpm 12 应该和 v11 行为完全一致,最大的变化是底层用 Rust 重写了。」命令行参数、lockfile 格式、node_modules 结构全部保持不变。你现有的 CI 脚本、.npmrc 配置、workspace 配置,不用动一行。

几个小的 breaking change 是关于正确性的:不再把 GitHub/GitLab 上的 git 依赖当作传输方式记录(而是当作身份),workspace.yaml 里有未识别配置会直接报错而不是静默忽略,依赖循环的打断方式变得更规范。都是之前行为不对的地方,不改大多数项目碰不到。

升级命令一行:

pnpm self-update next-12

GitHub 官方 latest 标签目前仍指向 v11.25,需要用这个命令才能拉到 v12。

为什么是 Rust,不是 Go 或别的

pnpm 不是第一个用 Rust 重写自己的 JS 工具。Bun 把核心从 Zig 换成了 Rust(也是为了内存安全),Rolldown 用 Rust 重写了 Vite 的打包层,Tailwind CSS v4 也把引擎换成了 Rust 的 Oxide。

但 pnpm 的动机有一点不一样。Kochan 在回应 vlt CEO Darcy Clarke 时说过一句话,被很多人截图保存:「用 Rust 重写 pnpm 比迁移到 ESM 还要快。」这句话不是自夸,是吐槽。ESM 迁移折腾了前端工具链好几年,而把同一个程序用 Rust 重写一遍,反而更省事。这背后是 Rust 的工具链成熟了,AI 辅助代码转换也把大体积代码库的 1:1 翻译变成了可操作的事情。

下一步

如果你用的是 monorepo,直接跑升级命令,然后挑一个子项目量大的 CI job 掐个表对比一下。感受最明显的是有大量依赖的项目——冷装时间差几秒,热装时间从几百毫秒掉到几十毫秒,这种差距在 CI 日志里一眼就能看见。

pnpm 12 的路线图还没走完:fetch 和 link 已经默认 Rust 化,接下来是 headless frozen-lockfile 安装器,然后是完整的依赖解析(add/update/remove),最后才是 run/store/publish 这些命令。即使现在,核心的安装步骤已经全部在 Rust 里跑了。

评论区

0 条评论

登录后可评论。

铁锈·Rust工具链 12 阅读