我升级了 pnpm 11,有几个印象深刻的点

0 阅读6分钟

这个月 pnpm 发了 v11。本来想等 v12 的 Rust 引擎一起写,但试了一圈下来有几个改动确实值得单独说说。如果你还在犹豫要不要从 pnpm 10 升上来,这篇应该能帮你省点时间。

pnpm logo.jpeg

先说我碰到的第一个坑

升级完第一件事——Node.js 必须 >= 22。

倒也不算过分的要求,Node 22 去年就 LTS 了。问题是很多项目到现在还在用 20,特别是一些历史遗留项目或者云函数那边有运行时限制。我自己有个项目就是 node 20,跑 pnpm install 直接报错。

所以如果你是那种 CI 镜像比较久没动的,或者项目 Dockerfile 还挂在 centos 上(pnpm 11 要求 glibc 2.27+),升级前多留个心眼。

另外 pnpm 11 自己变成纯 ESM 了,CommonJS 的分发包直接砍掉。对日常使用来说没什么感觉,你正常 pnpm install 就完事。但如果你写的脚本有依赖 pnpm 内部模块,那得注意一下。

供应链安全这块,总算不是摆设了

pnpm 从 v10 开始就有安全方向的动向了,但 v11 算是一次正经的落地。

新包有冷却期

之前 Shai-Hulud 那种供应链攻击大家应该还有印象——攻击者先发几个合法版本积累信誉,然后突然放一个带后门的版本,CI 自动拉取,直接中招。

pnpm 11 的做法简单粗暴:新包默认延迟 24 小时才能被解析安装minimumReleaseAge 默认 1440 分钟,等于给攻击者的快攻踩了脚刹车。就算有人发了恶意版本,你有一整天等社区去发现它。

24 小时是不是太长?如果有紧急修复要靠刚发的新版本,可以用 pnpm audit --fix 加白名单绕过去。或者 pnpm-workspace.yaml 里写一行 minimumReleaseAge: 0 直接关掉。看你自己权衡。

非标准来源默认阻断

blockExoticSubdeps 现在默认是开的。exotic 指的是从 Git 仓库直接拉、或者 tarball URL 装的传递依赖。这些不走 npm 注册表,registry 管不到,攻击者最喜欢拿来偷偷塞东西。

说实话,多数项目不会大量用到这种 exotic 依赖,所以这个默认动作影响面不大。但确实堵了一个口子。

构建脚本统一管了

之前 pnpm 有五个散落的配置项控制构建脚本执行,v11 统一成了 allowBuilds 一个字段:

# pnpm-workspace.yaml
allowBuilds:
  electron: true
  core-js: false
  esbuild: false

生命周期脚本攻击一直是被用得最多的路子之一。这个改动本身不复杂,但好处是看一眼配置文件就知道哪些包有执行权限,不用去翻 docs 找那几个参数在哪。

终于不用再调 npm publish 了

从 pnpm 出生那天起,pnpm publish 底层一直在调 npm publish。v11 终于把这个历史包袱甩掉了。publish、login、logout、view、deprecate、unpublish、dist-tag、version 这些命令全部原生实现,不再依赖 npm CLI。

实际好处是两个:

  • 少一层依赖,少一个攻击面。以前 npm CLI 出 bug 可能波及 pnpm 用户,现在没这个问题了。
  • OTP 环境变量从 NPM_CONFIG_OTP 改成了 PNPM_CONFIG_OTP,Web 认证支持扫码登录。

真用起来感觉清爽不少,不依赖 npm CLI 的发布流程干净很多。

SQLite 替掉了上百万个 JSON 文件

pnpm 的 Store 架构升到了 v11。原来 store 里每个包会对应一个 package.json 文件,项目多了数量级上去就是天文数字。现在全合并到单个 SQLite 数据库,manifest 信息直接存进去,安装时不用反复读文件。

还有几个值得说的小改进:

  • HTTP 请求从 node-fetch 换成了 undici,顺手做了 Happy Eyeballs(双栈连接)
  • 已知大小的 tarball 下载预分配内存,省去反复拷贝
  • CAS 文件直接写到内容寻址路径,不经过临时文件再重命名的流程。官方说冷安装能少约三万次 rename 系统调用
  • 全局虚拟存储开了以后,95% 的包在 Node.js 升级或者架构变更时不用重新导

实际体验:有缓存和 lockfile 的 warm install 跑到了 2.3 秒。比以前快了不少。

全局安装终于隔开了

pnpm add -g 现在行为变了。每个全局包有自己的目录、package.json、node_modules 和 lockfile,互相隔开。之前在全局装两个 peer dep 冲突的工具会打架的情况,应该会少很多。

二进制文件放在 PNPM_HOME/bin 子目录下,不再直接丢进 PNPM_HOME。shell 补全也干净了点。

SBOM 顺带提一下

pnpm 11 加了 pnpm sbom 命令,能生成 CycloneDX 1.7 或 SPDX 2.3 格式的软件物料清单。如果你在做合规审计或者客户指名要 SBOM,那用得上。不然大概率用不着。

审计的两个变化

pnpm audit 有两个地方变了:

  1. --fix=update:直接在 lockfile 里更新修复版本,不再靠加 overrides 硬修。这个方法直觉多了。
  2. 切到了 GHSA 标识。npm 注册表废弃了老的 CVE 审计端点,所以审计脚本里有 CVE 条目的,需要换成对应的 GHSA。

配置搬家

.npmrc 现在只管鉴权和注册表设置。pnpm 特有的配置全部搬到 pnpm-workspace.yaml 或新的全局 config.yamlnpm_config_* 环境变量也换成了 pnpm_config_*

官方估计也知道这些变动烦人,准备了一个 pnpm-v10-to-v11 codemod,大部分配置能自动转换。

Rust 引擎快来了

pnpm 团队在搞下一代引擎 Pacquet,基于 Rust 重写,预计 v12 落地。先做 fetch 和 link,后面再覆盖依赖解析。

官方放了一组数据:lockfile-only 无缓存安装,Rust 引擎 3.1 秒,pnpm 11 是 4.7 秒。Warm install 更夸张——902 毫秒对 2.3 秒。虽然还没到生产就绪的程度,但这数据确实诱人。

package manager 这几年的竞争,从 npm 一家独大,到 Yarn 加速,到 pnpm 效能和安全一起抓,再到 Bun 杀进来——越来越有意思了。


总结:新项目直接上 pnpm 11;现有项目啃下 Breaking Changes 清单再规划。最需要留意的是 Node.js 版本和配置迁移,别的大头可以用 codemod 扫掉。


如果你觉得这篇有用,欢迎来我的博客 Aura Imagai 逛逛,主要写前端、AI 和工程化相关的东西。

参考来源: