感谢神奇的 Semver 动态规则,npm 社区经常会发生依赖包更新后引入破坏变更的情况(应用没有使用依赖锁的话),而应用开发者就要在自己的依赖声明里先临时绕过,避免安装到有问题的版本,如果是一级依赖,只需要改 package.json 的声明就可以了,但如果是子依赖,就需要进行版本重写(overrides/resolution)了。
本文是一篇针对版本重写功能的指南性文章,当你遇到如下的问题时,就可以按照对应的依赖重写语法,解决这些依赖问题了:
-
临时版本修复:需要临时把有问题的子依赖(依赖的依赖)版本降级或升级到正常的版本上。如降级有 Breaking Change 的 @babel/generator。
-
强制升级子依赖:使用的 一个包更新频率较慢,想更新其子依赖时。如一个老的构建依赖依赖的 css-loader 需要更新到新版本。
-
依赖多版本统一:React/Webpack 等包出现多个版本,造成构建或运行时问题,需要统一项目对应依赖的版本到一个版本上。
-
子依赖重写:某个子依赖 A 依赖特定版本的 B 时会造成问题,仅需要重写依赖 A 下 B 的版本时。
-
删除子依赖:想删除某些子依赖不正确的依赖时,如子依赖把 devDependencies 写到 dependencies 里,或者写错 semver 的,开发者又没有权限发布这个不规范的包,需要删除或者替换子依赖的 Semver 声明。如不规范的依赖包中把应该在 devDependencies 里的包写在了 dependencies 里。
-
一级依赖:直接声明在 package.json 的 (dev | optional | peer) dependencies 中的依赖。
-
子依赖:一级依赖的依赖,无法由应用开发者直接指定版本。
-
嵌套重写:指仅重写某个依赖下的一级依赖或者子依赖(限制 Scope 重写依赖版本)。
阿里的前端开发者使用的包管理软件,除了社区常用的 npm, yarn 以及 pnpm 外,还有阿里内部特有的 tnpm 及其不同 mode。他们各自都有不同的依赖安装模式及依赖版本重写的语法,按大版本及语法的不同,把他们分为如下几类:
npm
yarn
tnpm
pnpm
npm@5-6
tnpm@8 (npm mode)
npm@8
tnpm@9 (npm mode)
yarn@1 (classic)
tnpm@* (yarn mode)
yarn@3 (berry)
tnpm@*
pnpm@<6.3.1
pnpm@>6.3.1
依赖重写语法
不支持(需 Hack)
overrides
resolutions
resolutions (yarn 3 语法)
resolutions (yarn 1 语法)
resolutions (pnpm 语法)
pnpm.overrides
锁文件版本
package-lock.json@1
package-lock.json@2
yarn.lock@1
yarn.lock@6
不支持
pnpm-lock.yaml
本部分针对每一种语法都进行了详细的解释与简单的案例,在最后还有实际的例子可以供读者更好的理解使用场景。如果是紧急解决问题,建议先看对应包管理软件的语法,补充参考案例,以更快的解决问题。
注:该字段主要在 npm 8 最新版上被支持,npm 7 及以下的版本中并没有这个能力,不过到还是有办法在旧版 npm 上用上这个能力,如果一定要使用 npm 6,请跳转到下一个子标题 Npm 6 Overrides (Hack)。
基本语法
使用前记得更新 npm 到最新版:npm i -g npm@latest。
基本的语法有点类似于 CSS 的选择器 - 声明语法,选择器用于匹配到包,声明用于重写版本声明。
在如下的例子中,示范了所有选择器和重写版本的语法:
这个例子中的声明,都是全局替换,无论是子依赖还是子依赖的依赖,只要匹配到选择符,就会应用规则。
嵌套重写
除了全局替换之外,也可以对特定包的子依赖进行重写。在嵌套结构中,用 “.“ 代表嵌套的包本身,有点像 less 里的 &。
当出现多个规则应用到同一个包上时,嵌套越深的规则优先级越高:
最后安装的结果是:
-
所有 foo 包的版本被设定成 1.0.0。
-
foo 下的所有 boo 包被设定成 3.0.0。
-
除了 foo 下的,所有其它的 boo 包被设定成 1.0.0。
另外需要注意的是,成文时的 npm 版本有个小问题,在嵌套重写中判断一个规则是否被删除时,判断的是父依赖的规则是否存在,下面的变动将不会把原来的嵌套重写规则从锁中删除:
首次重写:
仅删除嵌套重写,锁文件依赖保持被重写的状态:
删除父级依赖的声明,才会把依赖状态修正成无修改的状态:
特殊规则
Overrides 有几条特殊规则,一般在简单情况下遇不到,但还是要学习下,以避免发生预期外的行为。
首次从 package-lock v1 升级到 package-lock v2 时,overrides 可能不生效
如开头的表格中提到的,package-lock v1 是 npm@6 生成的锁,package-lock v2 是 npm@8 生成的锁。在升级 Node 或者主动升级 npm 后,可能见到下面这个提示:
overrides 可能并没有生效,重新再跑一次 npm i 就可以了。
另外,当选择器带版本范围时(如 "ms@^2": "1"),从老锁升级的新锁也有可能不应用 overrides 规则(是 npm 的 bug,如果用到了要观察一下锁文件更新的状态)。如果没生效,把版本范围去了,或者删除锁重新生成即可。
一个相关的 Issue:https://github.com/npm/cli/issues/5051
重写不能与 dependencies 声明冲突
overrides 与各类在 package.json 中直接声明的 dependencies 不能出现冲突,如:
会出现 EOVERRIDE 的报错:
一个包只能被一条规则匹配
也可以理解为:当一个包先匹配了一个选择器后,就会停止后续的匹配。也即先遇到的规则优先级最高,如:
如果我们调转上面这个例子的顺序,会产生另一个问题:
比如依赖中有一个 debug@4,他会被重写到 debug@3,此时,要不要继续应用 debug@3 的重写规则呢?
答案是:会继续应用,因为重写之后重新从上至下匹配时,会先匹配到上面的规则,进而应用 ms 的重写规则。最后的结果是,debug@4 变成 debug@3,debug@3 依赖的 ms@2 也会被重写到 ms@1。
不过笔者非常不建议在 overrides 中这样写,会造成理解上的混乱。
一个包只要匹配了选择器或者重写的目标,就算该规则被匹配
成文时 npm@8.19 尚未完全实现这个能力,目前遇到多重重写的,这些重复的规则都会被忽略。但 overrides 设计文档中标注了这个行为,所以还是解释一下。
我们把上面的的例子再稍加修改,加入双层对包自身版本的重写:
注意:"debug": "3" 的语法只是 "debug": { ".": "3" } 的语法糖。
此时,debug@4.3.4 最终的版本是 4.3.3 还是 4.3.2 呢?
npm 引入了另一条规则:如果一个包匹配了选择器(debug@4.3.4),或者匹配了重写目标(debug@4.3.3),都算是这个规则被应用了。结合上面的遇到第一个应用的规则即停止的逻辑,就可以得到确定性的结果,即:
-
先匹配 debug@4.3.4,得到 debug@4.3.3。
-
进行下一轮匹配, debug@4.3.3 与首条规则的 ".": "4.3.3" 匹配,则 debug@4.3.4 整条规则视为已匹配,结束后续匹配。
-
最后的重写结果就是 4.3.3。
npm 6,7 本身并没有类似于 overrides 的能力,但我们可以利用 npm 8 的算法,生成一颗应用 overrides 规则的依赖树后,再生成 npm 6 兼容的锁文件,来间接达到在 npm 6 上使用 overrides 的能力。
利用 @ali/tnpm-lock-adapter@1.6.0 及以上的版本,可以帮我们利用 npm 8 的算法生成 npm 6 的锁。
配置好 overrides 后,在项目目录下执行如下命令,即可生成应用了 overrides 的兼容 npm 6 的 v2 版本 pacakge-lock.json,此时再利用 npm 6 进行安装,就可以安装到重写后的版本了。
项目如果要持续使用 npm 6,可以在 package.json 的 scripts hook 中配置上面 tnpm-lock-adapter 这个命令,以持续应用 overrides 规则,避免失效。
Yarn 1.x (Classic) 已不再持续维护,Yarn 现在活跃的维护版本是 3.x (Berry)。新老版本的重写语法不一样。
基本语法
与 npm 的选择器不同,yarn 仅支持匹配包名,不支持在选择器中使用版本范围来选择特定包的特定版本。语法与案例如下:
特殊规则
dependencies 声明的优先级高于 resolutions
如果 dependencies 中的一个依赖 a 出现在 resolutions 中,则这个依赖 a 的版本依然是按 dependencies 中的声明安装,但其它包的子依赖中的 a 则应用 resolutions 规则,如:
会得到如下的依赖树:
npminstall mode(默认安装模式)
npminstall 是 tnpm 8,tnpm 9 的默认安装模式。什么都不配的情况下 tnpm 跑的就是这种安装方式。与 yarn 1 的语法一致,不再赘述。
npm mode
即在 package.json 中配置了:
在 tnpm 8 中,npm mode 使用的 npm 版本为 6,不支持 overrides,如果仍需要使用,请参考本文 npm 6 overrides Hack 部分,并开启 lockfile。
在 tnpm 9 中,npm mode 使用的 npm 版本为 8,可以直接像用 npm 8 那样配置 overrides。
yarn mode
即在 package.json 中配置了:
tnpm 自带的版本是 yarn 1,如果仓库没有切换过 yarn 3,则使用的就是 yarn 1 的 resolutions 语法。
不过 yarn 因为自带了版本切换机制,如果你的仓库额外配置了 yarn 3,还是要按 yarn 3 的语法进行配置。
rapid mode
在 tnpm 9 中,使用 rapid mode 安装时:
无论模拟的文件目录结构是 npm(--by=npm) 还是 npminstall,重写的语法与 npm 8 的 overrides 一致,直接使用即可。
基本语法
与 yarn 1 的语法相似,不过在选择器中支持了版本,且不再支持重写嵌套依赖。
另外,可以用 yarn set resolution -s 来修改 resolutions 字段。
特殊规则
resolutions 的优先级比 dependencies 高
当这两个字段中都有同一个包时,dependencies 中的版本声明会被替换掉。
基本语法
pnpm 的语法与 yarn 1 类似,但又不完全一样;另外 "pnpm.overrides" 和 "resolutions" 字段的功能相同,后者是前者的别名,直接看例子:
特殊规则
overrides 的优先级高于 dependencies
如果 dependencies 和 overrides 中都声明了一个包的版本,则 overrides 会覆盖 dependencies 中的声明。如:
会生成如下的 pnpm-lock.yaml
嵌套依赖只会重写一级,不会覆盖全部子依赖
与 yarn 和 npm 的重写规则不同,pnpm 在嵌套使用时,只会重写一级,有点类似于 Less 中的 “>” 语法。如有这样的依赖结构:
在使用如下 overrides 重写时:
会得到这样的依赖树,仅有 qar 的直接依赖被重写了。
假设 @babel/core 依赖的 @babel/generator@7.19.0 出了 Bug,应用需要临时降级到 7.18.0。
npm/tnpm npm mode overrides
tnpm/yarn1/yarn3/pnpm resolutions
node-saas 因为其对 Node 版本有强要求,所以经常会在升级 Node 的过程中出现 node-sass 无法安装的问题(gyp Build Error)。
首先我们建议应用开发者更换 sass 实现到 dart-sass,可以从 sass 官网上获取。如果是子依赖中的 node-sass 版本与 node 冲突,则可以使用依赖重写来解决这个问题。
如原使用的 node-sass 为 4.12 ,不支持 Node 16+,即可利用重写强行写到 6.x。假设有如下的依赖树:
npm/tnpm npm mode overrides
tnpm/yarn1/yarn3/pnpm resolutions
一些不规范的包会引入低版本的 webpack,与其它依赖引用的高版本 webpack 冲突,进而造成问题。如下面这个案例,因为项目中存在多个 webpack 版本,提升(hoist)依赖后,根目录的 webpack 版本不一样,导致另一个不规范引用 webpack 的包(mini-css-extract-plugin)引用的版本不兼容。
要修复,需要将项目中的 webpack 版本保持统一,或者升级 @ali/jstracker 中的 webpack 版本。
npm/tnpm npm mode overrides
tnpm/yarn1 resolutions
yarn3 resolutions
pnpm overrides
不知道 npm 有一天能不能真的像它们代码里写的,干掉其它包管理软件,减少点前端开发者需要学的工具的数量……但看目前 Node 的发展趋势,是打算通过 Corepack 和 packageManager 字段,来兼容不同项目下不同的包管理软件了。
不过也正是因为各有所长,才会百花齐放,One for All 的包管理软件还道阻且长呢。前端同学们学不动也得学,毕竟这就是前端生态的特色之一嘛!
-
npm overrides 文档
-
npm Dependency resolution overrides RFC
-
yarn resolutions 文档
-
yarn 1 Selective Versions Resolution RFC
-
pnpm overrides 文档
-
yarn 3 resolutions 文档
-
yarn 3 cli set resolution
-
https://ascii-tree-generator.com/
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/hd-nodejs/24769.html