npm 版本控制

查看依赖包版本

通过执行以下命令能够查看某个依赖包的最新版本。

# 查看某个 package 的注册信息
npm view <package-name>
# 查看某个 package 的最新版本
npm view <package-name> version
# 查看某个 package 在 npm 服务器上所发布过的版本
npm view <package-name> versions
# 查看仓库依赖树上所有包的版本信息
npm ls

语义化版本规范

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

标准版本

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

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

版本是严格递增的,例如:16.2.0 -> 16.2.1 -> 16.3.0 -> 16.4.0

先行版本

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

先行版本的格式是在修订版本号后面加上一个连接号(-),再加上一连串以点(.)分割的标识符,标识符可以由英文、数字和连接号([0-9A-Za-z])组成。

例如:

# 格式
major.minor.patch-{identifier}.{identifier}.{identifier}
# 示例 1:通常第一个 identifier 为版本号标签,关于版本号标签请看下文
# 常用于先行版本的版本号标签是 beta、rc、experimental
1.0.3-alpha.1

版本号标签

常用的版本号标签如下:

版本号标签语义说明
latest默认不指定版本情况下默认安装的最新版本
alpha内测内部测试版,一般不向外部发布,会有很多 BUG,一般只有测试人员使用
beta公测也是测试版本,这个阶段的版本会一直加入新的功能,在 Alpha 版之后推出
next下一个
rc候选Release Candidate 系统平台上就是发行候选版本。RC 版不会再加入新的功能了,主要着重于除错
experimental实验

给版本号添加标签的方式有两种:

# 第一种方式:在发布时指定标签
# 1. version-tag 替换为版本号标签的名称
npm publish --tag <version-tag>
# 示例
npm publish --tag next react@18.0.0
npm publish --tag experimental vue@3.2.0
# 第二种方式:发布后执行以下命令
# 1. package-name 替换为 npm 包名字
# 2. version 替换为 npm 包的指定版本
# 3. version-tag 替换为版本号标签的名称
npm dist-tag add <package-name>@<version> <version-tag>
# 示例
npm dist-tag add react@18.0.0 alpha
npm dist-tag add vue@3.2.0 next

版本号大小比较

格式:major.minor.patch-[pre-release]+[build-metadata]

版本号大小比较是从左往右依次比较 major、minor、patch、pre-release。build-metadata 对优先级无影响。

现行版本优先级低于正式版本,如:

  • 1.0.1-alpha.1 < 1.0.1
  • 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0

版本号大小与发版时间有关系吗?答案是 没有关系

例如:依次发布 1.0.0-alpha.11.0.0-alpha.31.0.0-alpha.2。用户安装了 1.0.0-alpha.1,此时升级版本会安装 1.0.0-alpha.3

版本号升级与标签之间的关系?

执行 npm install <package-name>npm update <package-name> 等同于执行 npm install <package-name>@latestnpm update <pacakge-name>@latest。用户当前版本低于 latest 标签下的最高版本,而低于其他标签下的版本时,会安装其他标签下的版本。

例如:

  • 用户安装了 1.0.0-alpha.1(alpha),此时发布了 1.0.0(latest),用户升级后的版本是 1.0.0(latest)
  • 用户安装了 1.0.0-alpha.1(alpha),此时发布 1.0.0-beta.0(beta),用户升级后的版本时 1.0.0-beta.0(beta)
  • 用户安装了 1.0.0(latest),此时发布了 2.0.0-alpha.1(alpha),用户升级后的版本不变,仍然是 1.0.0(latest)

版本工具使用

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

# 安装工具
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.23'); // true
semver.minVersion('>=1.0.0'); // '1.8.0'

更多关于 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-name> 安装包时,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。

除此以外,还包含以下规则:

  • >:接受高于指定版本的任何版本
  • >= 接受等于或高于指定版本的任何版本
  • <=:接受等于或低于指定版本的任何版本
  • <:接受低于指定版本的任何版本
  • =:接受确切的版本
  • -:接受一定范围的版本,例如 2.1.0 - 2.6.2
  • ||:组合集合,例如 < 2.1 || > 2.6

可以合并其中一些符号,例如 1.0.0 || >= 1.1.0 < 1.2.0 即使用 1.0.0 或从 1.1.0 开始但低于 1.2.0 的版本。

锁定依赖版本

实际开发中,经常会因为各种依赖不一致而产生奇怪的问题,或者在某些场景下,我们不希望依赖被更新,建议在开发中使用 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 会升级所有的红色依赖。

依赖版本控制的最佳实践

参考资料