
一、pnpm 12 值得升吗pnpm 12 值得升。这话我说得干脆因为不是看发布说明得出的。11.2.2 和 12.8.1 装到同一台机器上同一个项目两边各跑一轮写着写着发现发布说明里有两类一类我一测就对上一类我怎么测都对不上。这两类得分开讲。装依赖慢起来不同的人碰到的地方不一样。CI 上等一次 install按分钟算node_modules 把磁盘吃掉一块删了心疼留着占地换包管理器还得先想想流水线脚本里哪几条命令要重写。pnpm 靠硬链接省磁盘、把依赖结构管得很死成了很多人的默认选择。v12 做的最大一件事是全量 Rust 重写官方原话是彻底告别 TSNode 实现。升级之前我心里有三样担心。兼容性最要命。命令行、配置、lockfile、node_modules 这四样只要有一处对不上升级就不是十分钟能收场的事而发布说明给的承诺是几乎零迁移成本。数字也容易糊弄人。我给自己定了个规矩宣传材料里的倍数一律跳过只看带场景的原数。再就是规模。这些收益落到多大的项目上还有效发布说明举的例子都是大仓库。这篇按性能、多语言、依赖管理、安全、工程化五块讲升级方案和验证放在最后一节。二、换的是实现接口没动先看这张对照表。维度v11v12实现语言TypeScript 实现纯 Rust 原生重写运行时依赖依赖 Node22 运行脱离 Node 环境交付形态npm 安装的包独立可执行文件安全与索引基础安全能力与 SQLite 索引供应链防护默认开启与 SQLite 单文件索引命令行参数、配置文件格式、lockfile 结构、node_modules 布局这四个是 pnpm 对外的契约。契约不动内部从 TypeScript 换成 Rust你的仓库就不用跟着改。要是它顺手把配置格式也重做一遍今天这篇就得改叫迁移指南了。「独立可执行文件」这句我动手看了。v12.8.1 的包里躺着一个 59.7MB 的 ELF 文件里面能找到 rustc 的路径把它拷到一个只有系统命令的 PATH 下照样打印版本号。我这份 v11.2.2 也是单个可执行文件146MB里面塞着 Node 运行时同样不需要外部的 node。所以「脱离 Node」不是 v12 独有的本事两份单文件发行版都做得到区别在包里装了什么以及后面要说的内存账。四行里先看运行时依赖。v11 每次调用都先把 Node 拉起来这笔开销落在每一次 install 上仓库越大越明显。那 7 处行为差异会不会砸到我头上我逐条对过官方公示多数跟边缘用法有关正常项目碰不到。公示里写的是命令行、配置文件、lockfile、node_modules 目录结构四项完全兼容另列出仅 7 处微小行为差异绝大多数项目无需改代码。这类差异花半小时自查还是划算的。升之前把 lockfile 复制一份升完拿它跑一次 frozen-lockfile 安装看有没有被改写再跑一遍平时最容易飘的那几条命令比如带筛选的脚本、带自定义目录布局的构建。差异藏在这些地方不在主流程里。你用到了哪些边缘写法只有你自己清楚。三、同一台机器上两个版本差多少敢直接往项目上套的数字只有一组所以我自己开了个台子。环境摆在这儿能不能照搬你自己判断。机器是 WSL2 里的 Linux x86_64Node 24.16.0pnpm 两边分别是 11.2.2 与 12.8.1registry 走 registry.npmjs.org基准项目 15 个直接依赖装完 node_modules 约 43MB。两个版本各用独立的 store互不污染。口径这块最容易被含糊过去我说细一点。三个场景。重复安装指依赖没变、node_modules 和 store 都在再跑一次 pnpm install干净全量安装指删掉 node_modules、store 留着首次安装指两个都清空、老实走网络。重复安装那一组跑 10 次取均值单次太快计量工具的精度只有 10ms测一次不算数另外两个场景各跑 3 次取中位数。我测到的结果是这样。场景pnpm 11.2.2pnpm 12.8.1变化重复安装10 次均值427ms21ms约 20 倍干净全量安装3 次中位数0.78s0.08s约 10 倍首次安装3 次中位数4.89s4.03s约 1.2 倍重复安装峰值内存133MB20MB约 6.6 倍干净全量安装峰值内存491MB38MB约 13 倍首次安装峰值内存586MB62MB约 9.5 倍内存的变化比时间更显眼峰值内存最多差到十几倍方向都是往下走。装完的 node_modules 体积两边都是 43MBlockfile 测完没被改写。缓存预热那组官方说 472ms 变成 15ms我测到 427ms 变成 21ms量级和比值几乎一样。干净全量安装那组他们说 8.2s 到 5s我这儿对应的是首次安装4.89s 到 4.03s方向一致差在依赖数量和网络速度上。至于 Vercel 那边 21 项目集群提速 64.4%~90.5%我没有在本机复现这类规模的仓库只能引用不能背书。有一句我没能对上。他们说 v12 用全新 SQLite 单文件索引替代海量 JSON 索引。我翻了两个版本的 store都是 v11/index.db 这一个 SQLite 文件json 文件数是零两边一模一样。这个说法更像是在跟 v9 及更早的版本比拿来说 v11 到 v12 的变化对不上。你要是冲着这一点升级可以先放下。速度从哪来发布说明给了两条因果我逐条核了一遍。交付形态换成原生二进制可执行文件脱离 Node 直接运行规避了 Node 启动开销索引换成全新 SQLite 单文件索引替掉海量 JSON 索引查询更快。前一条站得住后一条在你这一代版本里已经发生过了。内存是另一回事。速度是体验差异内存是硬失败。官方把依赖解析 OOM 崩溃单列了一条说明这类崩溃在超大依赖图上真的发生过。v11 的依赖解析在依赖图很大时会把内存吃满项目大到某个规模就走到那一步。v12 把这一阶段的内存占用压了下来能扛住更大的依赖图。我这边 491MB 到 38MB 的差距跟这个方向对得上。你那边会不会撞上翻翻 CI 的日志有没有那种跑一半被 kill 的构建比看任何倍数都准。想拿自己的项目核一遍把下面这段抄走就行。PNPM11/path/to/pnpm11/pnpmPNPM12/path/to/pnpm12/pnpm# 两个版本各跑一遍这里以 $PNPM12 为例换成 $PNPM11 再来一次日志分开记# 重复安装依赖和 store 都不动连跑 10 次取均值foriin$(seq110);do/usr/bin/time-v$PNPM12install2warm-12.logdone# 干净全量安装删掉 node_modulesstore 留着跑 3 次取中位数rm-rfnode_modulesforiin$(seq13);do/usr/bin/time-v$PNPM12install2clean-12.logdone/usr/bin/time -v连内存峰值一起给第三节那张表里的内存行就是这么记的。基准项目是 15 个直接依赖axios、express、typescript 这一批你不必照抄换成自己仓库的依赖在项目根目录跑口径一致就能比。四、三种语言进同一个 Workspace这条比提速值钱如果这次升级只能留一条我留多语言。pnpm 12 把 npm、Python、Cargo 三类依赖放进同一个 Workspace一条 pnpm install 全装上不用为了跑一个 Rust 组件再切一次包管理器。前端仓库里塞 Python 脚本、塞 Rust 工具的情况不少见混着用就得两套工具链这一条比提速数字更实在。省下的不只是一条命令。版本以前靠口头约定或者在 README 里写一句现在落进同一个 lockfile跟着仓库走。Python 补得比我预期的多。以前在 CI 上跑 Python 任务得先把解释器准备好现在 pnpm 自动下载适配版本的 Python 解释器本地不用提前配置环境。另外两处支持是 Python 可编辑包与多平台统一 lockfile。团队里 macOS 和 Linux 上装出来的依赖版本不会分成两套评审的时候少一类争吵。Cargo 这边目标是让前端 Monorepo 能无缝混合 Rust 代码。混进来之后麻烦的是版本pnpm 12 的做法是统一版本管理与依赖锁定把两边工具链之间的适配冲突压到配置层解决而不是留给你在本地各自应付。配套的还有 pnpm pipeline。我翻了它的 help它读 pnpm-workspace.yaml 里的 pipelines 段默认跑名为 default 的那条做的是模拟 CI 流水线、批量执行 Workspace 任务可替代部分 Turbo 脚本能力。它替掉的是脚本编排那一段前提是你的任务能被 Workspace 描述清楚构建缓存、远端缓存这些 Turbo 擅长的东西它不接。你手上要是只有前端仓库多语言这一段可以先跳过。五、日常省事的那几处这几处都不改你的用法只是每天少几步。从 --save-types 说起。TS 项目里装业务包经常还得顺手补一个 types漏了就得回头翻代码。这种错难度低、频率高很难一次改对只能靠工具堵。v12.6 给的解法是装业务包时自动匹配安装对应的 types/* 类型包手动这一步可以省掉。我看了 12.8.1 的 add 帮助写的是给没有自带类型的包补上 devDependencies 里的 types。它不是默认行为得自己加上。体积和搬运也省了事。pnpm 12 会做全自动依赖去重重复的依赖收掉锁文件条目和 node_modules 体积一起降。还有个变化是可迁移 node_modules打包和部署阶段能直接搬。这条我没有在本机验证过官方把它算成 v12.6 的一项变化。它的好处在换机器、换流水线的时候不用重装一遍CI/CD 里缓存、解压、再校验那几步能省掉一些。还有 catalog。本地私有包和本地调试包以前管着别扭版本要么写死要么随缘。现在 catalog 目录支持file:与link:两种协议调试期的包和发布后的包共用一份描述内部包多的工作区感受最明显。最后一条是后来加的。pnpm add 可以直接吃 Package URL依赖来源不走常规 registry 的场景多一条路。内部构建产物挂在某个地址上省掉先发包再装的两步。你手上有没有这种反复补包的动作六、默认值前移我试了它的下限安全这块的改动一句话能说完默认值变了。这点我认也是这次容易被忽略的好事。minimumReleaseAge:1dblockExoticSubdeps:true两项供应链防护现在是默认开启的。minimumReleaseAge 默认 1d意思是新发布的包一天之内不许装。npm 投毒抢的是首发那段时间窗口作者刚发出来的版本装的人最少、审查最少这段时间最容易得手。blockExoticSubdeps 管子依赖来源不常规的子依赖会被自动拦下来依赖树深处被塞东西的机会少了。两项都不用你手动配装完就在。这条从 v11 继承v12 又强化了一轮。我试着找了一下它的下限。找一个 3 小时前刚发布的版本装上v12.8.1 装成功了没有任何提示v11.2.2 也装成功但它在 pnpm-workspace.yaml 里写了一条白名单并说明这是松散模式放行的结果。再把窗口显式设成 3 天同一个版本立刻被拦下来退出码 1。测下来默认形态是提示加白名单不是硬拦。发布说明那句默认禁止要打个折扣看。我也保留一点。默认开启意味着你得知道它在那儿。急着用当天刚发布的包它会拦你一下你会不会以为是网络问题这个代价我认安全默认值比事后救火便宜。要提防的是团队里有人顺手加个忽略开关那这套默认值就白设了。同一个文件里我还试了另一件事写错一个设置名会怎样。v11 静默忽略什么都不说v12 会警告并猜出我想写的可能是哪个键。要是我在 package.json 里用 devEngines 钉住了 12.8.1它就变成直接失败退出码 1并解释项目钉住的版本满足当前运行版本这些设置不可能是给别的版本用的。以前写错的键不出声现在会报到脸上这是升级后容易碰到的一类变化。锁文件那边也有变化。tar 包哈希校验失败会直接报错不像旧版静默覆盖 lockfile。这个变化改的是失败发生的时机。旧版静默覆盖结果是 CI 和本地装出两套依赖两边对不上一查就是半天而且你根本不知道是哪次装的开始不对。新行为把问题挡在安装阶段lockfile 不会在你不注意的时候被改写两边装出不同依赖的情况也就少了。你的项目里有没有人写过一条没人记得原因的忽略配置七、工程化四条里只有两条我现在用得上先说 peerDependencies告警噪音大多从这儿来。版本一不匹配告警冒一串动手改又怕影响别人拖着又被下一个人拖着。噪音的代价是让人对告警脱敏之后真出问题也不看了。pnpm update --peer 是专门为这件事加的指令我确认了它的说明更新对象就是 peerDependencies。以前要么手改要么写个脚本半处理有了这条指令脚本可以退休了。它有个前提你的项目得真在使用 peer 依赖纯应用仓库大概感受不到。再看调度器。pnpm 12 支持自定义任务依赖关系也支持单任务并发数量控制资源被抢占的情况能靠配置压下去。仓库里包一多就知道有用该串的串起来该并的并起来机器不会白白被并行压死。跨平台构建多了个 forceIgnoresPlatform 配置用来抹平多系统之间的构建差异。Windows、Mac、Linux 三边的人构建结果对不上是这种仓库里最伤信任的问题。配置能覆盖一部分场景剩下的还得靠 CI 兜底。剩下那两条是内部逻辑修复pnpm deploy 与 --filter 的筛选逻辑重写过大仓库里按包筛选、分包打包、批量部署不容易在半路出岔子。日常察觉不到出问题的时候才知道有没有用。八、升级一条命令验证三步升级这件事命令只有一条。pnpmself-update它会把你带到 v12 最新稳定版。要提前做的是把 CI 镜像里的 pnpm 版本一起更新掉不然流水线上跑的还是老的。他们的结论是现有项目无需改动配置、代码、lockfile 与 node_modules 都保持原样那 7 处微小差异在文档里逐条对照就行业务侧基本察觉不到。确认没升坏我按三步走每步都有判据。判据比步骤重要升级之后的第一周人容易把没感觉当成没问题。第一步看版本确认自己确实在 v12 上。pnpm--version第二步测安装。把缓存清掉再装一遍跟升级前的耗时比。判据是干净安装落在升级前的七成以内能进这个区间前面那组数字在你这儿就算成立。pnpminstall--frozen-lockfile第三步跑工程任务确认筛选、打包、部署这几条流程还是通的。pnpm-rbuild三步都过再合主分支。测试多的仓库可以再加一步把测试跑一遍。lockfile 对不上时安装阶段就会直接报错不会拖到运行时。我的做法是先拿一个小仓库走完这三步再动主仓库。九、你手上的项目属于哪一类三类的账不一样分开算。个人开发者拿到的东西最直接。依赖一变再跑一遍等待时间接近于零。依赖图大的个人项目不用再撞上依赖解析 OOM 崩溃。类型安装那一步省掉手补 types 的动作样板少写几行。中小型项目更容易算。升级是零成本的命令行和配置照旧安全那两项默认值往前挪了一截兼容风险基本可以排除。团队里没人在意包管理器的时候这类升级最容易被拖。我的判断是越早越好办改动量不会随项目变老而变小。大型 Monorepo 与跨语言项目吃到的收益最多。性能质变、多生态统一管理还有 CI/CD 上的效率在这类项目上一起放大。依赖图越大重复安装省下的时间越多。内存那条也在这类项目上更突出我测到干净安装的峰值内存差十几倍仓库再大一圈这个差距只会更值钱。结论是这样所有使用 pnpm 的项目都建议升级区别只在先后。依赖图大的、跨语言多的排在前面。要不要今天就动你比我清楚你手上那条流水线有多脆。十、我的选择和保留三条主线我先摆出来。Rust 重构带来性能质变多语言生态打破边界安全与工程能力各往前走了一步。这三条我都能在上面找到出处。零迁移配上这些收益是这次最特别的地方。工具链迭代大多先加能力再补坑这一次把兼容性放在了前面我把它算成一次优质迭代。我的选择是先动一个不关键的小仓库走完上面那三步再决定主仓库什么时候跟。要是你手上只有一个仓库那就直接升出问题当场能回滚代价比犹豫低。保留的地方也写清楚。只有一台机器、一个依赖集、一次测量官方那组 Vercel 集群的数字我没有复现发布窗口只探了下限内存那条只看了峰值。换一台机器、换一套依赖数字会变方向大概不会。pnpm 这套东西的边界也在变。它从前端包管理器起步现在也管 Python 和 Cargo 的依赖后面大概还会往别的语言伸慢慢变成全栈项目工程化管控工具。前端的活它做前端之外的活也接。这是我现在的判断pnpm 再走一两代上面这些数字大概都要重测。你手上那份 CI 里install 那一步占多少时间