前言

背景

一个听说,一个身边例子,一个血泪教训。

听说:之前在一个开发群里听说他们的项目因为在线上 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.zx 是主版本号,在有不兼容 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.jsonpackage-lock.json 保持不变,那么及时依赖有了更新,我们每次下载的版本也是完全一致的。

关于 package.json 的功能,其实随着 npm 版本的变化也是经历了一些变化的,详情参考这个回答

至于 yarn.lock, 和 package-lock.json 文件的功能基本一致,不过这个文件诞生的时间比较早。而我也习惯于使用 Yarn 作为包管理工具,因此使用这个文件的机会要多一些。不过副作用就是,如果团队中有一个人使用了 Yarn,则其他人也要使用 Yarn,否则就起不到锁版本的作用了。

最佳实践

  1. 团队统一使用 NPM 或者 Yarn 来管理项目;
  2. 在使用 Git 管理项目版本时,一定不要忽略这两个文件;

锁版本可能会带来的问题

当然,解决一个问题往往会带来新的问题,锁版本也是一样。锁版本会带来诸如旧版本依赖存在安全隐患无法解决,或者无法引入新功能等一系列问题。至于这个问题如何解决,并不在本篇文渣中讨论,我们将在以后展开探讨。

参考资料