Nodejs成功离不开 npm 优秀的依赖管理系统。在介绍整个依赖系统之前,必须要了解 npm如何管理依赖包的版本,本文将介绍 npm包 的版本发布规范以、何管理各种依赖包的版本以及一些关于包版本的最佳实践。

# 查看npm包版本

你可以执行 npm view package version 查看某个 package 的最新版本。

执行 npm view conard versions 查看某个 package 在npm服务器上所有发布过的版本。

执行 npm ls 可查看当前仓库依赖树上所有包的版本信息。

# SemVer规范

npm包 中的模块版本都需要遵循 SemVer规范——由 Github 起草的一个具有指导意义的,统一的版本号表示规则。实际上就是 Semantic Version(语义化版本)的缩写。

SemVer规范官网: https://semver.org/

# 标准版本

SemVer规范的标准版本号采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须以数值来递增。

  • 主版本号(major):当你做了不兼容的API 修改
  • 次版本号(minor):当你做了向下兼容的功能性新增
  • 修订号(patch):当你做了向下兼容的问题修正。

例如:1.9.1 -> 1.10.0 -> 1.11.0

# 先行版本

当某个版本改动比较大、并非稳定而且可能无法满足预期的兼容性需求时,你可能要先发布一个先行版本。

先行版本号可以加到“主版本号.次版本号.修订号”的后面,先加上一个连接号再加上一连串以句点分隔的标识符和版本编译信息。

  • 内部版本(alpha):
  • 公测版本(beta):
  • 正式版本的候选版本rc: 即 Release candiate

# React的版本

下面我们来看看 React 的历史版本:

可见是严格按照 SemVer 规范来发版的:

  • 版本号严格按照 主版本号.次版本号.修订号 格式命名
  • 版本是严格递增的,:16.8.0 -> 16.8.1 -> 16.8.2
  • 发布重大版本或版本改动较大时,先发布alphabetarc等先行版本

# 发布版本

在修改 npm 包某些功能后通常需要发布一个新的版本,我们通常的做法是直接去修改 package.json 到指定版本。如果操作失误,很容易造成版本号混乱,我们可以借助符合 Semver 规范的命令来完成这一操作:

  • npm version patch : 升级修订版本号
  • npm version minor : 升级次版本号
  • npm version major : 升级主版本号

# 版本工具使用

在开发中肯定少不了对一些版本号的操作,如果这些版本号符合 SemVer规范 ,我们可以借助用于操作版本的npm包semver来帮助我们进行比较版本大小、提取版本信息等操作。

Npm 也使用了该工具来处理版本相关的工作。

npm install semver
  • 比较版本号大小
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
  • 判断版本号是否符合规范,返回解析后符合规范的版本号。
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
  • 将其他版本号强制转换成semver版本号
semver.valid(semver.coerce('v2')) // '2.0.0'
semver.valid(semver.coerce('42.6.7.9.3-alpha')) // '42.6.7'
  • 一些其他用法
semver.clean('  =v1.2.3   ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.minVersion('>=1.0.0') // '1.0.0'

以上都是semver最常见的用法,更多详细内容可以查看 semver文档:https://github.com/npm/node-semver

# 依赖版本管理

我们经常看到,在 package.json 中各种依赖的不同写法:

  "dependencies": {
    "signale": "1.4.0",
    "figlet": "*",
    "react": "16.x",
    "table": "~5.4.6",
    "yargs": "^14.0.0"
  }

前面三个很容易理解:

  • "signale": "1.4.0": 固定版本号
  • "figlet": "*": 任意版本(>=0.0.0
  • "react": "16.x": 匹配主要版本(>=16.0.0 <17.0.0
  • "react": "16.3.x": 匹配主要版本和次要版本(>=16.3.0 <16.4.0

再来看看后面两个,版本号中引用了 ~^ 符号:

  • ~: 当安装依赖时获取到有新版本时,安装到 x.y.zz 的最新的版本。即保持主版本号、次版本号不变的情况下,保持修订号的最新版本。
  • ^: 当安装依赖时获取到有新版本时,安装到 x.y.zyz 都为最新版本。 即保持主版本号不变的情况下,保持次版本号、修订版本号为最新版本。

package.json 文件中最常见的应该是 "yargs": "^14.0.0" 这种格式的 依赖, 因为我们在使用 npm install package 安装包时,npm 默认安装当前最新版本,然后在所安装的版本号前加 ^ 号。

注意,当主版本号为 0 的情况,会被认为是一个不稳定版本,情况与上面不同:

  • 主版本号和次版本号都为 0: ^0.0.z~0.0.z 都被当作固定版本,安装依赖时均不会发生变化。
  • 主版本号为 0: ^0.y.z 表现和 ~0.y.z 相同,只保持修订号为最新版本。

1.0.0 的版本号用于界定公共 API。当你的软件发布到了正式环境,或者有稳定的API时,就可以发布1.0.0版本了。所以,当你决定对外部发布一个正式版本的npm包时,把它的版本标为1.0.0。

# 锁定依赖版本

# lock文件

实际开发中,经常会因为各种依赖不一致而产生奇怪的问题,或者在某些场景下,我们不希望依赖被更新,建议在开发中使用 package-lock.json

锁定依赖版本意味着在我们不手动执行更新的情况下,每次安装依赖都会安装固定版本。保证整个团队使用版本号一致的依赖。

每次安装固定版本,无需计算依赖版本范围,大部分场景下能大大加速依赖安装时间。

使用 package-lock.json 要确保npm的版本在5.6以上,因为在5.0 - 5.6中间,对 package-lock.json的处理逻辑进行过几次更新,5.6版本后处理逻辑逐渐稳定。

关于 package-lock.json 详细的结构,我们会在后面的章节进行解析。

# 定期更新依赖

我们的目的是保证团队中使用的依赖一致或者稳定,而不是永远不去更新这些依赖。实际开发场景下,我们虽然不需要每次都去安装新的版本,仍然需要定时去升级依赖版本,来让我们享受依赖包升级带来的问题修复、性能提升、新特性更新。

使用 npm outdated 可以帮助我们列出有哪些还没有升级到最新版本的依赖:

  • 黄色表示不符合我们指定的语意化版本范围 - 不需要升级
  • 红色表示符合指定的语意化版本范围 - 需要升级

执行 npm update 会升级所有的红色依赖。

# 依赖版本选择的最佳实践

# 版本发布

  • 对外部发布一个正式版本的npm包时,把它的版本标为1.0.0
  • 某个包版本发行后,任何修改都必须以新版本发行。
  • 版本号严格按照 主版本号.次版本号.修订号 格式命名
  • 版本号发布必须是严格递增的
  • 发布重大版本或版本改动较大时,先发布alpha、beta、rc等先行版本

# 依赖范围选择

  • 主工程依赖了很多子模块,都是团队成员开发的npm包,此时建议把版本前缀改为~,如果锁定的话每次子依赖更新都要对主工程的依赖进行升级,非常繁琐,如果对子依赖完全信任,直接开启^每次升级到最新版本。
  • 主工程跑在docker线上,本地还在进行子依赖开发和升级,在docker版本发布前要锁定所有依赖版本,确保本地子依赖发布后线上不会出问题。

# 保持依赖一致

  • 确保npm的版本在5.6以上,确保默认开启 package-lock.json 文件。
  • 由初始化成员执行 npm inatall 后,将 package-lock.json 提交到远程仓库。不要直接提交 node_modules到远程仓库。
  • 定期执行 npm update 升级依赖,并提交 lock 文件确保其他成员同步更新依赖,不要手动更改 lock 文件。

# 依赖变更

  • 升级依赖: 修改 package.json文件的依赖版本,执行 npm install
  • 降级依赖: 直接执行 npm install package@version(改动package.json不会对依赖进行降级)
  • 注意改动依赖后提交lock文件
阅读全文