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

422次阅读  |  发布于2年以前

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

"dependencies": {
    "dayjs": "1.11.3",
    "lodash": "~3.9.2",
    "vue": "^2.6.2"
}

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

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

一、某中小厂线上真实升级事故

公司后台管理类项目,用的ui框架为「iview」「package.json」中的版本号为:

"dependencies": {
   "view-design": "^4.5.0"
}

2021-06-01日完成一个功能开发,该功能包含一个可以「拖拽」Modal弹框,效果和代码如下图:

image.png

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

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

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

image.png

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

经过对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的文档,在他的更新日志里发现了这样一段话:

image.png

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

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

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

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

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

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

二、package.json中版本控制详解

  1. 「npm包的版本号构成:」

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

4 5 0
重大更新(如重新设计、功能重构等) 次要更新(如新增组件、特性升级等) 补丁更新(如小bug修复、细节优化等)

是不是对npm版本号已经有一定理解了呢,在此基础上,我们再来介绍下 「^」「~」 两个符号的作用:

2 . 「^和~两个符号的诞生:」

我们先想象下如果版本安装都是「固定」的将会是什么样的场景。「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诞生记[1])

四、总结

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

参考资料

[1]https://reacttraining.com/blog/react-router-v5/#why-the-major-version-bump: https://link.juejin.cn?target=https%3A%2F%2Freacttraining.com%2Fblog%2Freact-router-v5%2F%23why-the-major-version-bump

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8