前言

pnpm v8.0.0-alpha.0 版本已经发布,包含少量变化,但其中还是有令人在意的点的。

本文将默认读者拥有大部分 pnpm v7 版本的知识储备,进行 v8 版本的前瞻速攻。

安装方法

目前通过指定 Tag 方式可以安装 v8 alpha 版:

npm i -g pnpm@next-8

所有 Tag 详见:npm > pnpm version

由于距 pnpm v8 正式发布还有一段距离( alpha > beta > latest ),本文可能存在部分内容过时,请仔细甄别。

从 v6 升级至 v7

本文是 v7 升级 v8 ,若你需从 v6 升级,可先参考 :

  • 聊聊 pnpm v6 升 v7 拥抱的变化和经验谈

正文

下面对 changelog 逐条分析。

auto-install-peers is true by default

在 pnpm v7 版本之初,strict-peer-dependencies 是默认打开的,这会导致不安装 peerDependencies 就会安装报错(除非打开 auto-install-peers),引发了大量社区反馈,极大降低了 pnpm 的易用性,我们在 v6 升级 v7 的文章中也推荐设定 strict-peer-dependencies=false 避免不必要的报错问题。

所以 strict-peer-dependenciesv7.13.5 又调整回默认 false

v7 的计划失败了,到了 v8 ,可以看出 pnpm 希望通过默认打开 auto-install-peers=true 的方式解决 peerDependencies 的问题,但这会导致在本地 package.json 中的 peerDependencies 也被安装,一个例子如下:

// package.json{"peerDependencies": {"react": "^18"}}

此时我们不期望 react 被安装,因为我们没有在 dependencies / devDependencies 指明安装他,但由于 auto-install-peers 被默认打开,最终仍然会安装 react

进一步,在实际业务中,分情况考虑可能造成的影响:

  1. 普通场景:在本地开发时,按照开发规范,我们必然对 peerDependencies 的依赖也在 devDependencies 指定一份,但不在 dependencies 指定,所以该依赖是肯定会被安装的,auto-install-peers 不会对我们造成影响。

  2. monorepo 场景:在 monorepo 时我们有 Monorepo丝滑方法论 ,依赖总唯一,所以无需担心 auto-install-peers 问题。

通过分析我们得知 auto-install-peers=true 默认打开一般情况对我们没有实质性影响。

下面我们举两个会存在问题的 case 供参考:

  1. npm link 调试等情况,导致 peerDependencies 被意外安装,但 npm link 早已过时不再使用,这种情况是很少见的。

  2. 不遵守 npm 包开发规范,导致 peerDependencies 被意外安装,我们也不展开该极少数的情况。

由于 auto-install-peers 几乎不会对我们造成影响,加上使用包管理工具应该向 零配置 靠拢,本文不再推荐手动关闭 auto-install-peers 该选项,而是拥抱变化,若有极少数的特殊需求,你可能需要关闭该行为:

// .npmrcauto-install-peers=false
The registry field is removed from the resolution object in pnpm-lock.yaml

lock 文件层面格式的优化,对我们使用没有直接影响,可以忽略。

save-workspace-protocol is rolling by default

save-workspace-protocol 决定了在 pnpm monorepo 中,手动安装工作区内同名包的版本添加行为:

pnpm add foo

如我们的 workspace 含有 foo 该包,在 v7 我们安装该包得到的是:

"foo": "workspace:^1.0.0"

现在 v8 默认 rolling 策略得到的将是:

"foo": "workspace:^"

由于我们极少在工作区内手动运行安装同名包的命令,往往是手动添加 workspace 内的其他包,并且使用 * 格式:

// package.json// 手动添加要使用的 monorepo 内其他包"foo": "workspace:*"

所以此变化不会影响我们的使用,可以忽略。

When there’s a files field in the package.json, only deploy those files that are listed in it

该命令涉及到 pnpm deploy 相关,由于 prune 功能还不够成熟,考虑到没有得到 nextjs 示例项目的推荐,我们几乎不会使用,可以忽略。

Use lockfile v6 by default

v6 是 pnpm 的下一个大版本的 lockfile 格式,目前格式版本是 5.4

// pnpm-lock.yamllockfileVersion: 5.4// ...
// pnpm-lock.yaml (pnpm v8)lockfileVersion: '6.0'// ...

6.0 lockfile 格式相比 5.4 有 80% 内容基本一致,变化主要集中在对依赖的 版本描述 上,以下给出一个大致的 demo :

// old 5.4/@ant-design/colors/6.0.0:resolution: { ... }dependencies:'@ctrl/tinycolor': 3.4.0
// new 6.0registry.npmjs.org/react@18.0.0:resolution: { ... }name: reactversion: 18.0.0engines: {node: '>=0.10.0'}dependencies:loose-envify: ...dev: true

可以看出 6.0 新版 lockfile 对依赖的描述从 单行 变成了 多行 ,但对我们实际使用没有影响,运行 pnpm i 刷新 lock 文件即可,该变化可以忽略。

注:pnpm 早在 v7.24.2 就预先放出了该 6.0 lockfile 版本的配置项,同时官方仓库也第一时间使用了 6.0 新版 lockfile 格式,没有 Breaking change ,可以放心使用。

resolve-peers-from-workspace-root is true by default

resolve-peers-from-workspace-root 该选项主要用来解决 monorepo peerDependencies 困境 问题,当多子包需要同一个 peerDependencies 时,无需手动配置提升,而是在 root 安装唯一的该依赖,即可保证全局唯一性。

但提升至 root 只适合要求的版本范围全部匹配,可以唯一提升的依赖,不支持精细版本控制,同时会污染 monorepo 根 root ,造成不必要的隐形全局依赖和心智负担,我们在 Monorepo管理方法论和依赖安全 中明确指出 严禁安装 monorepo 全局依赖 ,故此选项不推荐使用,同时我们有 Monorepo丝滑方法论 实现更好的 monorepo 多实例解法,轮不到 resolve-peers-from-workspace-root 发挥作用,可以忽略该变化。

需要注意的是, Monorepo丝滑方法论 往往和框架挂钩,对于小型 monorepo 项目,若支持不了 丝滑方法论 的实现,可以勉强接受 resolve-peers-from-workspace-root 这种解决 peerDependencies 困境的方法。

publishConfig.linkDirectory is true by default

随着 package.json#exports 的流行,更多的项目会采用非根发布 npm 包(如 jotai 等),publishConfig.linkDirectory 是对于此类项目的优化支持,不会直接影响到我们的使用,可以忽略。

resolution-mode is lowest-direct by default

近年 npm 供应链攻击的 case 偶然发生,目前比较好的 被动 管控手法有:

  1. 预打包依赖:参考 Nextjs / Umi 预打包策略,实现略。

  2. pnpm time-based :time-based 策略可以基于包的最后发布时间来确保你使用的依赖在某个发布时间前,从而规避供应链发新包导致的攻击问题,但对于非自建 npm registry 需要花费更多时间读取 npm 包元信息,目前 pnpm 官方采用此策略,此处不做展开。

  3. pnpm lowest-direct :lowest-direct 是另一种 pnpm 推出的 缓解 供应链攻击的手段,他等价于锁定你的直接依赖版本(解析为 semver 的最低依赖版本),比如 ^1.0.0 也只会安装 1.0.0 最低版本,而不是最新的 ^1 版本,这等价于你锁定该依赖到 1.0.0 ,若你需要 1.2.0 版本也需要写为 ^1.2.01.2.0 ,该行为可以从一定程度上 缓解 供应链发新包导致的攻击,也不会承受 time-based 策略拉取 npm 元信息造成的依赖安装变慢。

默认 lowest-direct 策略后,我们可以直接性的 缓解 供应链攻击造成的影响,即使在没有 lock 文件时也可以每次都安装到确定的依赖版本。

但相应的成本是:必须显示的提升依赖版本号来做依赖升级,否则无法安装到最新版本,使用解析到的最低版本极有可能遭受未知的 bug ,从而浪费大量排查时间,请在使用 pnpm v8 时格外注意手动升级依赖至最新版本防止未知的 bug

Direct dependencies are deduped. So if the same dependency is both in a project and in the workspace root, then it is only linked to the workspace root

该变动是为了配合 resolve-peers-from-workspace-root 的行为,忽略即可。

Create a lockfile even if the project has no dependencies at all

对我们没有直接影响,忽略。

总结

通过分析 pnpm v8 alpha 的变化我们发现,只要遵守 pnpm v7 我们总结出来的 monorepo 方法论:

  • Monorepo 丝滑方法论:引用模块热更新

  • Monorepo 管理方法论和依赖安全

从 v7 升级至 v8 是几乎无损、水到渠成顺利的。

另外,请留意在 v8 状态下你的依赖是 锁定等价 的最低版本,请定期升级版本防止不必要的 bug ,并在出现 bug 时升级依赖排查。

若你需要进一步了解 monorepo ,提供如下内容参考:

  • monorepo 工作流基础之 changesets 打开与进阶(Speeches)

  • pnpm monorepo 的技术选型临界点(Critical adoption)

  • Monorepo 设计思路(Speeches)

  • turborepo v1.2.0版本升级指南

以上。

Pnpm Beta 版追加

Date:2023-03-07

pnpm v8.0.0-beta.0 已经释出,新增了 dedupe-peer-dependents 选项,用于缓解 peerDependencies 重复问题,现在以下配置搭配将在 v8 中默认开启:

# 优先从 root 解析 peerDependencies ,尽可能的提升唯一性resolve-peers-from-workspace-root=true# 去除重复的 peerDependencies ,尽可能减少重复性dedupe-peer-dependents=true

pnpm 通过这两个配置,可以尽力而为的缓解 peerDependencies 多实例、重复对项目造成的致命问题。

但他仍然无法像 alias 重定位一样彻底保证项目单实例,同时无法解子包调试热更新问题,所以此处更推荐优先使用 Monorepo 丝滑方法论 ,由于丝滑方法论需要在 webpack 或内置于框架中实现,在 pnpm >= 7.29.0 版本下,若你环境受限无法落地丝滑方法论但又遭遇 peerDependencies 问题时,可以打开这两个选项缓解 peerDependencies 问题。