12k
All articles

深入解析 pnpm 的 Rust 重写

了解 pnpm 12 的 Rust 重写、性能测试结果、与 pnpm 11 的兼容性,以及可能影响 CI 安装的升级风险。

OpenReplay Team
OpenReplay Team
深入解析 pnpm 的 Rust 重写

pnpm 12 用原生 Rust 实现取代了 pnpm 原有的 TypeScript 代码库,同时沿用 pnpm 11 的命令、参数、配置项和 lockfile 格式,因此大多数项目无需修改任何配置即可升级。

如果你在 monorepo 和繁忙的 CI 集群中使用 pnpm,大概已经看到过”提速 90%“之类的标题,并想知道背后有什么代价。对于一款负责写入 lockfile 的工具来说,新的大版本值得比一张基准测试图表更仔细的审视。

本文将介绍:为什么 JavaScript 编写的包管理器本身就慢,Rust 引擎改变了什么、又付出了什么代价,每项数据由谁测得,以及哪些行为变更可能影响你的流水线。

核心要点

  • pnpm 12.0.0 于 2026 年 8 月 26 日发布稳定版。除少量有文档说明的差异外,它沿用了 pnpm 11 的命令、参数、配置项和 lockfile 格式。
  • 在 pnpm 官方基准测试页面(pnpm 11.27.1 对比 12.7.0)上,热缓存下的重复安装从 563 ms 降至 18 ms,而全新安装仅从 8.4 s 降至 4.4 s,因为冷安装的耗时主要花在网络传输和解包上。
  • Vercel 测得,其包含 1,670 个包的 workspace 在 pnpm 12 上的安装耗时比 pnpm 10.28 减少 64.4% 至 90.5%。但由于原生程序的下载体积更大,无缓存时的 Corepack 启动速度慢了 11.1%。
  • 最可能导致 CI 出问题的变更是移除了 pnpm install --resolution-only,请改用 pnpm peers check。

约束条件:同样的 pnpm,不同的引擎

pnpm 12 的设计初衷是让升级不像一次迁移。pnpm 12.0 发布公告将此列为目标,兼容性指南也确认,除少量差异外,pnpm 12 沿用了 pnpm 11 的命令、参数、配置项和 lockfile 格式。pnpm 12 还保留了内容寻址存储(content-addressable store),让各项目可以共享包文件,而不必复制。InfoQ 也指出,node_modules 的目录结构同样没有变化。

大多数重写项目会把新代码库当作修正旧设计决策的机会。pnpm 则把兼容性作为首要目标,甚至表示其文档同时适用于两个版本。表面上几乎什么都没变,真正的工作是替换底层的一切。

为什么 JavaScript 编写的包管理器会慢?

用 JavaScript 编写的包管理器在每次大型安装时都要承担两项开销:每次调用都要启动 Node.js 运行时,并且要让成千上万次文件系统操作都经过同一个 JavaScript 运行时。

一次安装大致经历以下几个阶段:

  1. 从 registry 获取包的元数据。
  2. 解析依赖图。
  3. 下载 tarball。
  4. 将其解包到存储中。
  5. 将包链接到 node_modules。

第一项开销是固定的。在 pnpm 11 做任何实际工作之前,Node.js 必须先启动。pnpm 12 以原生二进制形式发布,每个平台对应一个 @pnpm/exe.<platform>-<arch> 包。self-update 文档确认,运行时不会先启动 Node.js,因此这部分启动开销不复存在。

第二项开销随工作量增长。第 4 和第 5 阶段涉及解包 tarball 以及为成千上万个文件创建硬链接,而每一次此类操作都要经过 JavaScript 运行时。

这一原则适用于任何工具:当剩余工作很少时,消除固定开销的效果最明显。安装几乎无事可做时,启动时间占据了实际耗时的大部分;而当需要下载数百 MB 数据时,启动时间几乎可以忽略不计。

pnpm 12 到底快了多少?

pnpm 12 的性能提升是真实的,但并不均衡:热缓存下的重复安装从 563 ms 降至 18 ms,而全新安装仅从 8.4 s 降至 4.4 s。这些提升数据来自两组基线不同的独立测量,请勿将它们合并为一个数字。

场景升级前pnpm 12测量方基线
热缓存重复安装563 ms18 mspnpm 基准测试页面(pnpm 12.7.0)pnpm 11.27.1
全新安装8.43 s4.42 spnpm 基准测试页面(pnpm 12.7.0)pnpm 11.27.1
1,670 个包的 workspace,六种场景(中位数)不适用耗时减少 64.4–90.5%Vercel(pnpm 12.0.0)pnpm 10.28.0
无缓存的 Corepack 启动不适用慢 11.1%Vercel(pnpm 12.0.0)pnpm 10.28.0
有缓存的 Corepack 启动不适用快 74.7%Vercel(pnpm 12.0.0)pnpm 10.28.0

pnpm 的数据来自 pnpm 基准测试页面上的 alotta-files 项目,对比的是 pnpm 11.27.1 与 pnpm 12.7.0。该页面会定期重新运行,且始终显示各工具的最新版本,因此这些数字可能会变化。两行 pnpm 数据之间的差距,正是上一节所述原理的体现。热缓存安装提速约 30 倍,很可能是因为启动等固定开销在其运行时间中占比很大。全新安装只提速约 1.9 倍,因为大部分时间花在网络传输和 tarball 解包上,与引擎用什么语言编写无关。

Vercel 自己的测量结果也呈现同样的规律。在 node_modules 已存在、存储为热缓存且关闭脚本的情况下,安装耗时从 1.476 s 降至 142 ms;而在完全冷启动且启用生命周期脚本的情况下,安装耗时从 9.850 s 降至 3.472 s。每个中位数均来自同一台 Linux 机器上每个版本 20 次运行的结果,测试对象是一个包含 21 个项目的 Turborepo workspace。

pnpm 12 的原生构建也有代价。Vercel 测得 pnpm 12 的 Corepack 下载体积为 47.3 MB,而 pnpm 10.28.0 为 17.5 MB,并将无缓存时启动变慢归因于下载体积的增大。未缓存 Corepack 的 CI runner 很可能在每个任务中都要承担这部分开销。

与新引擎一同发布的确定性循环依赖处理是另一项独立的改进。pnpm 的兼容性指南将约 25% 的内存占用下降,以及在循环依赖较多的 workspace 中 2–3 倍的 peer 依赖解析提速,归功于这项改动,而非 Rust 引擎本身。

pnpm 的公开基准测试页面目前仅将 pnpm 12 与 npm 和 pnpm 11 进行对比。据 InfoQ 报道,由于基准测试环境存在问题导致排名不可靠,pnpm 已将 Bun 和 Yarn 从对比中移除。

为什么 pnpm 的 lockfile 格式必须保持不变?

pnpm 12 的 lockfile 格式必须保持不变,因为一旦 lockfile 格式不兼容,团队就会在升级过程中被割裂。如果 pnpm 12 写入新格式,所有开发者的电脑和 CI runner 都必须在同一天完成切换。否则,同一个仓库中就会有两种 lockfile 格式相互冲突,每个 pull request 都会夹杂着由最后运行的那个版本带来的无关改动。

保留格式是为了让团队可以逐步升级,但这并不意味着文件内容永远不会变化。格式和内容是两回事:

  • 现有 lockfile 继续可用。 指南指出,在出现需要重新解析的情况之前,pnpm 不会改动已有条目。
  • 重新解析可能改变条目。 pnpm 12 会以 HTTPS URL(而非 SSH)记录来自 GitHub、GitLab 和 Bitbucket 的依赖。它还会始终在相同位置切断依赖循环,从而使循环较多的 workspace 生成的 lockfile 更小。

请将在 pnpm 12 上首次触发重新解析的安装所产生的 diff 作为一个单独的 commit 进行审查。

升级到 pnpm 12 会导致哪些问题?

如果升级 pnpm 12 后 CI 任务失败,最可能的原因是某个脚本仍在调用 pnpm install --resolution-only。v12 会拒绝该参数,其报告 peer 依赖的功能现已由 pnpm peers check 承担:

# pnpm 11
pnpm install --resolution-only

# pnpm 12
pnpm peers check

v12 同样会拒绝 pnpm install --frozen-lockfile false。请使用 --no-frozen-lockfile 关闭 frozen-lockfile 模式,使用单独的 --frozen-lockfile 开启该模式。

其他变更主要影响执行结果,但其中三项也可能导致命令中止:

变更影响对象应对方式
GitHub/GitLab/Bitbucket 上的 Git 依赖通过 HTTPS 解析通过 SSH 访问的私有仓库在机器上配置 Git URL 重写
未知的 pnpm-workspace.yaml 配置键会被报告;当项目锁定了 pnpm 版本且当前运行的 pnpm 符合该版本时,命令会以 ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS 失败存在拼写错误的配置修正或删除该键
修改全局安装的命令在 sudo 下会以 ERR_PNPM_SUDO_NOT_SUPPORTED 失败sudo pnpm self-update 等命令不使用 sudo 运行
开启 engineStrict 时,常规 dependencies 中存在不兼容的 engine 会导致安装失败,即使它同时出现在 optionalDependencies 中使用 engineStrict 的项目原先只发出警告的安装现在会直接失败
Linux 上 packageImportMethod: auto 会优先尝试硬链接,再尝试 reflinkLinux 用户通常无需处理
全局的 node、deno 或 bun 会遵循项目锁定的版本安装了全局运行时的机器实际使用的将是锁定的版本

v12.0.0 发布说明解释了 workspace 检查的重要性。在 pnpm 11 中,如果 minimumReleaseAge 这类配置项出现拼写错误,pnpm 会悄无声息地跳过该键,导致本应生效的规则从未生效:

# pnpm-workspace.yaml
packages:
  - "apps/*"
minimumReleseAge:   # typo: pnpm 12 reports this key

兼容性指南共列出八项差异。其中六项会改变执行结果,另外两项会拒绝 pnpm 11 曾接受的命令行语法:--resolution-only 和 --frozen-lockfile false。上表并未涵盖全部内容,例如未涉及 pnpm 12 如何处理 pnpm add 中 yarn 这类包管理器名称;此外,workspace 配置键和 sudo 两行内容来自发布说明,而非兼容性指南。升级前请完整阅读该指南。

是否应该升级到 pnpm 12?

通常建议升级,而且升级过程会很平顺,这正是其设计目标。如果当前版本为 pnpm 11.10 或更高,运行:

pnpm self-update

如果项目通过 packageManager 锁定了 pnpm 版本,self-update 只会更新该字段中的版本号,而不会全局安装 pnpm。下次运行命令时,pnpm 会自动获取新版本。请提交这一改动,以确保 CI 使用相同版本:

{
  "packageManager": "pnpm@12.8.1"
}

请使用最新的 12.x 版本。如果你的安装流程依赖 pnpm deploy 或特定 linker 模式等不太常用的功能,请先在分支上试行升级。仍在使用 npm 的团队,在同时进行两项变更之前,可以先阅读从 npm 切换到 pnpm 是否值得。

总结

pnpm 12 保留了你日常使用的部分,包括命令、lockfile 格式和存储模型,并重建了其背后的引擎。这次重写之所以成效显著,是因为 JavaScript 版本受制于两项开销:每次调用时的 Node.js 启动,以及所有文件 I/O 都要经过单一运行时。升级前,请在 CI 配置中搜索 --resolution-only 和 --frozen-lockfile false,检查是否存在通过 SSH 访问的私有 Git 依赖,并将首次重新解析生成的 lockfile 单独提交,以便审查 diff。

常见问题

pnpm 12 运行时需要安装 Node.js 吗?

不需要。安装完成后,pnpm 12 以原生程序运行,因此无需 Node.js。独立安装脚本即使在安装阶段也不需要 Node.js。唯一的例外是通过 npm 安装 pnpm 12,此时安装程序需要 Node.js 22.13 或更高版本。如果你的平台没有预构建的 pnpm 12 二进制文件,pnpm 文档建议改用 JavaScript 版本的 pnpm 11。

在 pnpm 12 中如何继续通过 SSH 使用私有 Git 依赖?

pnpm 12 通过各托管平台的 HTTPS URL 获取 GitHub、GitLab 和 Bitbucket 依赖。如需继续使用 SSH,请配置 Git URL 重写,例如 git config --global url.'git@github.com:'.insteadOf https://github.com/。pnpm 在底层调用 git,因此该规则对 pnpm 运行的所有 Git 命令都生效。对于 pnpm 无法识别的托管平台以及包含凭据的 URL,pnpm 会完全保留你填写的原样,包括 SSH 地址。

为什么 pnpm 12 遇到无法识别的 pnpm-workspace.yaml 配置项时会失败?

只有当项目锁定了 pnpm 版本、且当前运行的 pnpm 与该版本相符时,才会失败。在这种情况下,pnpm 会将未知键视为错误,并以 ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS 中止。如果没有相符的版本锁定,则只会发出警告,命令继续执行。如果该键看起来是拼写错误,pnpm 会提示你可能想要设置的配置项名称。即使文件中存在错误的键,pnpm config 相关命令仍可正常运行,因此你可以借助它们定位并修复问题。

pnpm 12 中哪些命令在 sudo 下运行会失败?

在 sudo 下,pnpm setup、pnpm self-update 以及所有修改全局安装的命令(例如 pnpm add --global)都会以 ERR_PNPM_SUDO_NOT_SUPPORTED 中止。早期版本会悄无声息地改为写入 root 用户的主目录。全局包和配置都存放在你自己的主目录中,因此这些命令都不需要 root 权限。pnpm bin --global 等只读命令仍可在 sudo 下运行。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.