前言
背景
一个听说,一个身边例子,一个血泪教训。
听说:之前在一个开发群里听说他们的项目因为在线上 Mongoose.js 升级,导致 CPU 占有率飙升,最后经过长时间排查发现是升级所导致的。而且据说编号还是小版本升级,但是其实是大功能升级;
身边例子:广东的同事发现一个问题,线下功能跑得好好的,到了线上就不行,后来发现是线上的 Knex.js 版本太低,缺少当前使用到的方法所导致的;
血泪教训:今年 3 月 22 日晚上线站群的子站 CMS,项目由 Egg.js + Nuxt.js 组成,但是运行以后,结果却一直提示 Cannot get /
,连具体的错误信息都没有,根本无法排查。但是当时除了本地开发环境,测试环境也是完全可以正常使用的。因为这个问题大概搞了 6、7 个小时也没有定位原因,最后临时想了一个解决方案。结果到了一周以后,因为删掉了测试环境的项目,导致了测试环境和正式环境都无法正常访问,严重影响了上线进度,以及为了解决这个问题长时间奋战的我的心智。最后实在没有办法,在一行一行代码的调试中,突然发现,新安装的依赖和旧的依赖的代码有很大的区别。于是才找到了原因,只是因为 Nuxt 版本从 2.4.5 升级到了 2.5.0,升级的哪天,正好是我上周上线的那天……
环境
- Node.js:10.15.2;
- NPM:6.4.1;
- Yarn:1.15.2;
语义化的版本号
在描述如何解决版本不一致可能带来的问题之前,我们先解释一下什么叫做语义化的版本号。
其实很简单,主要的规范就是一个项目的版本号由三部分组成,即 x.y.z
。x
是主版本号,在有不兼容 API 的修改的时候才会更新;y
是次版本号,当有向下兼容的功能性新增的时候会更新;z
是修订号,一般来说是指修复缺陷、不怎么更新功能的时候更新。
更详细的信息可以参考这篇文章。
当然,这个规范是个大家约定俗成的,并不是强制性的。但是目前比较规范、流行的项目还是按照这个规范来更新版本号的,因此我们也可以通过版本号的变化大体预测一个项目会有哪些变化。
npm 下的版本控制
package.json
我们的 Node.js 项目一般都会由一个 package.json
文件来描述和管理。当前项目运行时可开发时所依赖的包也一样由该文件描述。
一个简单的 package.json
文件如下:
{
"name": "basic",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.22.2"
}
}
dependencies
下显示的就是我们当前项目所依赖的包,后面是对应的版本号。
版本号说明
我们通过安装包 npm install <package name>
命令执行以后,包所对应的版本号格式一般都是类似于 ^2.22.2
这样的。这个版本号表示,在别人也开发当前项目时,第一次使用 npm install
安装依赖时,会安装主版本号为 2 的最新版本;而与此相对, ~2.22.2
则表示会安装主版本号为 2,次版本号为 22 的最新版本。
更详细的说明可以看考 npm 官方的版本计算器。
版本锁定(2.30)
为何要锁版本
通过上述解释,我们不难发现,在不同的开发者之间、在不同的环境之间、在不同的时间点之间,我们下载依赖包的版本都有可能是不同的,这就回带来一个不常发生,但是发生了就注定是一场灾难的问题,即可能版本更新可能会带来 API 不兼容的问题,也许是主版本更新,也许是次版本更新,也不排除是修订号更新。
因此,为了保证当前项目的依赖高度一致,我们首先需要确保每次项目初始化下载依赖的版本高度一致。
如何锁版本
写死版本号
最直接的办法是修改 package.json
文件的依赖部分,去掉前面的符号,直接用明确的版本号替代。
package-lock.json 和 yarn.lock
幸运的是,这个问题不止我们会遇到,并且已经有了比较合理的解决方案。NPM 从 5 版本以后,就引入了 package-lock.json
的文件,在第一次安装依赖后,这个文件就会在目录下被自动创建。文件里面会有针对每一个依赖的版本信息。只要 package.json
和 package-lock.json
保持不变,那么及时依赖有了更新,我们每次下载的版本也是完全一致的。
关于 package.json
的功能,其实随着 npm 版本的变化也是经历了一些变化的,详情参考这个回答。
至于 yarn.lock
, 和 package-lock.json
文件的功能基本一致,不过这个文件诞生的时间比较早。而我也习惯于使用 Yarn
作为包管理工具,因此使用这个文件的机会要多一些。不过副作用就是,如果团队中有一个人使用了 Yarn
,则其他人也要使用 Yarn
,否则就起不到锁版本的作用了。
最佳实践
- 团队统一使用
NPM
或者Yarn
来管理项目; - 在使用 Git 管理项目版本时,一定不要忽略这两个文件;
锁版本可能会带来的问题
当然,解决一个问题往往会带来新的问题,锁版本也是一样。锁版本会带来诸如旧版本依赖存在安全隐患无法解决,或者无法引入新功能等一系列问题。至于这个问题如何解决,并不在本篇文渣中讨论,我们将在以后展开探讨。