前端Node

npm 语义化版本管理

鹏展博

2152字约7分钟

node

2023-05-13

前言

当我们着手进行项目开发时,为了方便项目管理,需要为项目定义一个版本。 可是,项目的版本号,应该怎么写呢? 是 1.0,还是 0.1 还是 1.0.0 还是 0.0.1 呢?

如果是使用了 1.0.0 作为起始,那么下一次版本,应该是 1.0.1 还是 1.1.0 呢?

我们经常在一些开源的项目中,看到一些项目的版本号,还携带了后缀, 比如 vue3.0.0-beta.0, 或者 react18.0.0-rc.0。这些后缀有代表了什么含义呢?

Semver 规范

使用一种好的版本号管理方案,可以帮助我们快速的了解版本的变化范围。

这并不是一个创新性的想法,实际上,你可能已经在做近似的事情了。问题在于只是“近似”还不够。 如果没有某个正式的规范可循,版本号对于依赖的管理并无实质意义。 将上述的想法命名并给予清楚的定义,让你对软件使用者传达意向变得容易。 一旦这些意向变得清楚,弹性(但又不会太弹性)的依赖规范就能达成。

在 开源社区中,由 Gravatars 创办者兼 GitHub 共同创办者 Tom Preston-Werner 建立了 语义化版本控制的规范, semantic version 简称 semver,即 这个规范称为 semver 规范

你可以在 https://semver.org/lang/zh-CN/ 了解 semver 规范的详细信息。

版本号格式

版本格式:主版本号.次版本号.修订号 ,我们以 X.Y.Z 作为 格式别名。

其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零,X 是主版本号、Y 是次版本号、而 Z 为修订号, 每个元素必须(MUST)以数值来递增。如 1.9.1 -> 1.10.0 -> 1.11.0

版本控制规范

  1. 标记版本号的软件发行后,禁止 改变该版本软件的内容。任何修改都 必须 以新版本发行。

  2. 主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。

  3. 1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。

  4. 修订号 Z(x.y.Z | x > 0)必须 在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。

  5. 次版本号 Y(x.Y.z | x > 0)必须 在有向下兼容的新功能出现时递增。 在任何公共 API 的功能被标记为弃用时也 必须递增。 也 可以 在内部程序有大量新功能或改进被加入时递增,其中 可以 包括修订级别的改变。 每当次版本号递增时,修订号 必须 归零。

  6. 主版本号 X(X.y.z | X > 0)必须在有任何不兼容的修改被加入公共 API 时递增。 其中 可以 包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号 必须 归零。

  7. 先行版本号 可以 被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。 标识符 必须 由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且 禁止留白。 数字型的标识符 禁止 在前方补零。先行版的优先级低于相关联的标准版本。 被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。 范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。

  8. 版本编译信息 可以 被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符来修饰。 标识符 必须 由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且 禁止 留白。 当判断版本的优先层级时,版本编译信息 被忽略。 因此当两个版本只有在版本编译信息有差别时,属于相同的优先层级。

    范例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。

  9. 版本的优先层级指的是不同版本在排序时如何比较。

    1. 判断优先层级时,必须 把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较 (版本编译信息不在这份比较的列表中)。

    2. 由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较。

      例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。

    3. 当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。

      例如:1.0.0-alpha < 1.0.0。

    4. 有相同主版本号、次版本号及修订号的两个先行版本号,其优先层级 必须 透过由左到右的每个被句点 分隔的标识符来比较,直到找到一个差异值后决定:

      1. 只有数字的标识符以数值高低比较。

      2. 有字母或连接号时则逐字以 ASCII 的排序来比较。

      3. 数字的标识符比非数字的标识符优先层级低。

      4. 若开头的标识符都相同时,栏位比较多的先行版本号优先层级比较高。

      例如:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。

总结递增规则如下

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

先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

FAQ

在 0.y.z 初始开发阶段,我该如何进行版本控制?

最简单的做法是以 0.1.0 作为你的初始化开发版本,并在后续的每次发行时递增次版本号。

如何判断发布 1.0.0 版本的时机?

当你的软件被用于正式环境,它应该已经达到了 1.0.0 版。如果你已经有个稳定的 API 被使用者依赖,也会是 1.0.0 版。 如果你很担心向下兼容的问题,也应该算是 1.0.0 版了。

语义化版本对于版本的字符串长度是否有限制呢?

没有,请自行做适当的判断。举例来说,长到 255 个字符的版本已过度夸张。 再者,特定的系统对于字符串长度可能会有他们自己的限制。

“v1.2.3” 是一个语义化版本号吗?

“v1.2.3” 并不是一个语义化的版本号。但是,在语义化版本号之前增加前缀 “v” 是用来表示版本号的常用做法。 在版本控制系统中,将 “version” 缩写为 “v” 是很常见的。 比如:git tag v1.2.3 -m "Release version 1.2.3" 中,“v1.2.3” 表示标签名称,而 “1.2.3” 是语义化版本号。

alpha、beta、rc

在规范中, 先行版本中 - 后的字符是自定义的,我们经常可以看到一些开源库使用的标识符如 alphabetarc

一般来说,这些 标识符表示以下含义:

  • alpha: 表示 内部测试版本。主要是提供给内部的开发和测试使用,不建议用户下载。
  • beta: 表示 公开测试版本。用户可以提前尝试使用一些功能。
  • rc: 表示预览版本。表示该版本不再增加新的功能,可能还存在一些 bug,修复完成后就到 正式版本了。

npm 指定版本范围

我们可以在 package.json 中看到版本号之前出现如 ^~ 等符号,如:

{
  "vuepress": "^1.9.7",
  "vue": "~2.7.11"
}

这些符号表示以后该库 安装版本的 允许范围。

通常常见的字符包括: ^~><<=>= 等。

^ 表示 次版本号的更新,比如 ^1.2.3 就表示以后安装的版本 >=1.2.3 <2.0.0。 但是如果 主版本号为 0,比如 ^0.2.3 相当于 >=0.2.3 <0.3.0^0.0.3 相当于 >=0.0.3 <0.0.4

~ 表示 修订号的更新,比如 ~1.2.3 就表示以后安装的版本 >=1.2.3 <1.3.0

完整版本范围说明,查看 npm 文档