两个真实线上升级故障让你彻底搞懂package.json中的脱字符

package.json相信大家都是熟悉的不能在熟悉了。我们经常在package.json中发现有这样的依赖项:

"dependencies": {
    "dayjs": "1.11.3",
    "lodash": "~3.9.2",
    "vue": "^2.6.2"
}
复制代码
dependencies依赖项里的包名作为key,value跟的是版本号。不过心细的同学会注意到:有的版本号前面加了 ~,还有的加了 ^。因为很多同学都是写业务,很少关注这些,平时一般情况下没问题,但是在一些特殊场景下,如果搞不懂版本号的细节和原理,会带来一些很头疼的经历。

接下来我们结合两个线上真实的故障案例,带大家彻底搞懂package.json中的版本号到底是怎么回事。

一、某中小厂线上真实升级事故
事故背景:
公司后台管理类项目,用的ui框架为iview。package.json中的版本号为:

"dependencies": {
   "view-design": "^4.5.0"
}
复制代码
在2021-06-01日完成一个功能开发,该功能包含一个可以拖拽的Modal弹框,效果和代码如下图:



这个功能在本地和测试环境都测试过了,没有问题。我们计划于2021-06-14日完成上线工作。

就在2021-06-14晚上,问题出现了~

线上的Modal弹框莫名其妙多了一个灰色蒙层!!如下图:



我和我的小伙伴都惊呆了,代码没动,而且测试环境都是好的,为什么线上会出问题呢?

事故原因分析:
经过对mask的配置排查,我们发现在Modal的代码里,写了一个mask=true的逻辑。

//  :mask="true"
<Modal v-model="modal12" draggable scrollable :mask="true" title="Modal 1">
    <div>Can be dragged off the screen</div>
</Modal>
复制代码
首先,我们批评了研发人员,没有mask的情况下,不应该把api写上去,需要保持代码的整洁。其次,我们开始很痛苦,为什么一份代码线上和本地测试表现不一致呢?

这时候我们想起,线上打包的时候,会把node_modules删除重新安装,本地和测试环境不会。

果不其然。我们把本地的node_modules删掉重新npm install的时候,复现了这个问题。

我们开始怀疑是iview的官方包出了问题。于是乎,我们去翻阅iview的文档,在他的更新日志里发现了这样一段话:



Modal 的属性 mask 在 draggable 模式下,不再强制设置为 false。

这个更新日志的日期发生在2021-06-11,正好是我们开发测试到上线的中间日期位置。

相信看到这里,很多同学已经能猜测到原因了,是的,这次事故的原因正是:

本地/测试环境和线上正式环境安装了不同版本(4.5.0 vs 4.6.0)的iview。

继续深究,那为什么本地/测试环境和线上正式环境会安装出两个版本号呢,难道版本号不是固定的吗?

是的,package.json中的版本其实是可控的,具体是怎么控制的呢,接下来就是  ^ 和  ~ 两个符号正式登场的时候了。

二、package.json中版本控制详解
npm包的版本号构成:
在学习 ^ 和  ~ 两个符号之前,我们需要先了解一下版本号的概念。所有的npm安装包的版本号都由三位数字组成,中间以.间隔。比如iview的版本号4.5.0。这三位数字都是有含义的,他们从左到右分别表示:重大更新.次要更新.修复补丁。我们用下图表示:

4    5    0
重大更新(如重新设计、功能重构等)    次要更新(如新增组件、特性升级等)    补丁更新(如小bug修复、细节优化等)
是不是对npm版本号已经有一定理解了呢,在此基础上,我们再来介绍下 ^ 和  ~ 两个符号的作用:






^和~两个符号的诞生:
我们先想象下如果版本安装都是固定的将会是什么样的场景。npm包也难免会有一些小bug要不定时修复,如果我们的版本号是固定的,那么势必有一点改动,我们就得改下版本号才能生效。这样效率是很低的。所以npm的机制规定:

npm 允许您接受更大范围的安装包版本,而不是在 package.json 中指定要安装的确切版本。

这种好处就是可以更加自由灵活,提高效率。有一些特性更新时,我们再也不需要手动更新版本号了。同样的,灵活的东西往往也会有弊端,如果我们不注意安装包的更新,可能就会带上上述的线上故障。

暂且抛开利弊不说,我们如何控制接受更大范围的版本号呢?答案就是我们上述提到的两个字符。

您可以使用波浪号 (~) 允许更新补丁级别版本(第三位数字)和脱字符 (^) 更新次要(第二位数字)或补丁级别(第三位数字)版本。

具体使用如下图:

符号    用法    版本    说明
(^)    ^3.9.2    3..    1. 向后兼容的新功能 2. 废弃特性,但是暂时保留 3. 特性更新/新增 4. bug修复补丁
(~)    ~3.9.2    3.9.*    1. bug修复补丁
但是有一点需要作为tips说明:

如果您没有安装过node_modules,那么npm install的时候,情况会按照您的标记符号(^/~)安装指定的最新版本。但是如果您的node_modules已存在,那么npm install的时候,情况会发生变化。运行npm install并不会重新检查是否有比您已经安装的更新版本可用。这时候,我们需要使用npm update命令,才能达到我们的更新目的。

相信看到这里,同学们应该对package.json中的脱字符(^)有了深入的理解了,上面的线上故障原因也彻底清楚了:

因为线上清除了node_modules,导致安装的是最新的4.6.0版本。而此版本中,mask属性已经不是写死的false。一旦写了mask=true,那么背后灰色的背景色就会出现,造成一个线上故障!

为了加深对package.json中版本号的理解,我们再来看一个更为严重,更为官方的故障。

三、react-router官方升级版本号设置冲突导致大规模应用出错:
2019年3月21日。

react-router官方团队,计划将react-router的4.3.x版本升级为4.4.0版本。他们做了一些特性升级。然而,react-router的另一个核心衍生库:react-router-dom,正好依赖了升级前的特性,而react-router-dom中的package.json恰恰为:

"dependencies": {
   "react-router": "^4.3.1"
}
复制代码
这个脱字符^刚好把react-router-dom依赖的特性更新为最新,且已经不可用的4.4新特性。

可想而知,react-router4.4版本发布完成之后,react-router-dom大面积报错。

最后在来不及修复的情况下,读者们可以猜一下,最后官方团队是怎么解决这个问题的。

他们最后在万般无奈之下,将react-router从4.4直接改成了升级到5.0。。。

最大的版本号更新了之后,脱字符^没有办法起到作用,问题也自然没有了,这就是react-router5诞生的一个小小插曲。

(原文链接:react-router诞生记)

四、总结
相信看到这里,对package.json中的脱字符(^),不管是理论层面还是实践层面,各位小伙伴应该都会有比较全面深入的认识了。在关键的时候,说不定可以为你提供很有价值的疑难杂症的线索。在以后的工作中,时不时也要留意下这个版本控制符号哦~

作者:掘金干货君

链接:https://juejin.cn/post/7121520457760653349




作者:掘金干货君


欢迎关注微信公众号 :深圳湾码农