好像一直没有写过版本号和依赖相关的内容,偶尔会有同学问,所以写一篇总结一下。
0. Semver
我们目前使用的版本规范通常基于 Semver,语义化版本。官方网站:语义化版本 2.0.0 | Semantic Versioning (semver.org)。按照其规则,版本号的结构应该是:
主(大)版本号
.次(小)版本号
.修正(补丁版本)号
。
其中,
- 主版本号一般包含架构和 API 的变化。如果 API 出现重大变化,使得依赖它的软件要重构,那么就要体现在大版本号里。不过现在的代码仓库很少有破坏式重构,API 一般能够在至少 2、3 个大版本里保持稳定。所以主版本号变化一般出现在大型重构时,仓库内部的代码架构和组织形式出现重大变化,或者基于不同系统,就需要升级主版本了。
- 次版本号一般表达功能变化。架构没有变化,原有 API 也基本维持不变,只是新增了功能。这个时候就会上调次版本号。
- 修订号一般表示修复 bug。
0.1 年代版本号
另一种流行的版本号规范是年代版本号,比如 Ubuntu,每年会发布两个版本,目前是 21.04,10月份会发布 21.10。偶数年的 .04 版本会最终成为 LTS,长期维护版本;奇数年的版本和 .10 版本则只维护半年,里面包含各种最新的软件组件,方便喜欢尝新的用户。
前端常见的软件和库,包括 node.js、Angular、Electron 也用这种方式确定版本号。
1. 依赖中指定版本号
我们在项目中可能会使用大量开源代码,这些开源代码通常都会使用包管理工具(比如 NPM,node package manager)安装和管理。
在 package.json 里,我们可以使用几个运算符告诉 NPM 我们希望怎么使用这些依赖:
- 不写,
repo: '1.0.0'
。要求使用 1.0.0 版本的 repo,必须完全一致。 ~
,repo: ~1.0.0
。要求大版本为 1,小版本为 0,修订版本不限制,比 0 高就可以。^
,repo: ^1.0.0
。要求大版本为 1,小版本高于 0 就可以。
一般来说,使用 npm i repo
安装的依赖,默认规则是 ^
;使用 npm i repo@version
安装的依赖,默认规则是写死。
2. 升级依赖
这个世界上不存在没有 Bug 的代码,也没有功能完善的代码。使用开源仓库,我们就要考虑升级依赖。一方面可以使用新功能,一方面可以解决 bug。
通常来说,直接使用 npm update
就能升级项目依赖,NPM 会按照(1)里设定的规则更新依赖。
3. 升级依赖的大版本
只使用 npm update
无法升级大版本。原因如上文所述,大版本可能包含破坏性的 API 更新,很容易导致 dev/build 失败,作为工具无法妥善处理,必须交给开发者手动完成。
很多同学因此不愿意升级大版本。但我建议大家还是要找机会做升级,尤其工具链,比如 webpack、babel。这些工具很多时候有时效性,长期不升级会导致各种问题。而且,升级工具链本身这也是我们偿还技术债务的好机会。
其实升级工具链的大版本并不复杂,大多数半天就搞定了;如果没有用到偏门功能,甚至可能直接升级就能跑。——通常来说,开源软件的作者面对使用频率高的功能,会比较保守;没人用的功能改动就会大刀阔斧一些。
升级依赖需要指定大版本,比如从 webpack 4 升级到 webpack 5,可以使用 npm i webpack@5
,这样会安装大版本是 v5 的最新版本。这里有个小建议,公司的生产级别的项目,最好不要着急升级,等到 X.2 X.3 这样基本稳定的次版本发布后再升级,可以避免踩很多坑。
一般来说,开源仓库的官方也会提供迁移指南,比如 webpack v4 to v5。只要你有 v4 的配置经验,照着指南操作,大多数时候都能顺利完成。
4. 解决 npm audit
问题
开源仓库的安全问题日趋严重,GitHub 和 NPM 都会帮我们检查依赖,并且根据已知的安全问题列表发出警告。所以在安装依赖时,我们经常能看到类似下面这种警告信息:
found 5 moderate severity vulnerabilities
run `npm audit fix` to fix them, or `npm audit` for details
这个时候,我们应该执行 npm audit
查看所有的审计结果,可能得到如下的报告:
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ Moderate │ Regular expression denial of service │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package │ glob-parent │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in │ >=5.1.2 │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ @vue/cli-service [dev] │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path │ @vue/cli-service > webpack-dev-server > chokidar > │
│ │ glob-parent │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info │ https://npmjs.com/advisories/1751 │
└───────────────┴──────────────────────────────────────────────────────────────┘
这个报告说明有问题的包是 glob-parent
,它由 webpack-dev-server 引入,又因为 @vue/cli-service 而最终成为项目的依赖。很明显,它是 @vue/cli 的组成部分,是前端工具链的一环。那么通常来说,它的危害层级很低,多半不会影响到项目整体安全性。
如果是 node.js 项目,就要小心了,服务器和后端都是安全重灾区,后果可能会很严重。这个时候,你可以往上翻,找到这些报告的最前面,如果可以修复,NPM 会告诉你应该运行什么命令更新问题依赖。如果不能修复,就要自己想办法了。
比如,你可以移除出问题的依赖,不过多半不可行。或者,我比较常用的方案是,在 GitHub 上找到依赖仓库,自己 fork 一份,然后升级其中的依赖,然后发布一个我个人的版本,接下来就用我自己的版本。等到官方修复后,再用回官方版本。
5. 总结
最后按惯例总结一下。依赖是我们软件产品的重要组成部分,它们的版本事关重大,必须给予足够的关注。至少,每个月要检查一次依赖的安全审计问题,如有需要,就升级依赖。
我们自己写代码的时候,也要遵守 Semver 的规定,适时上调版本号,让版本号能表示代码的发展情况。
欢迎吐槽,共同进步