📦 探索npm的语义化版本:充满诗意的冒险
现实世界是复杂的,我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的时间及烦恼。
语义化
语义化 是一个计算机科学和信息技术领域的术语,它指的是将数据、代码或其他信息表示为具有明确语义含义的形式。
往往我们在编程环境中都与它有过接触,例如:
- 在编程中,语义化通常涉及到使用有意义的命名、注释和结构,以便让代码更容易理解和维护。
- 在数据存储和检索中,语义化则通常涉及到使用元数据和标记来描述数据的含义和关系。
总的来说,语义化的目标是让计算机系统能够更好地理解和处理信息,从而提高效率和减少错误。
简介
在目前流行的前端研发模式中产生了一个新的概念,前端工程化 。前端工程化已经几乎成为当下影响最大的前端研发模式。目前主流的前端框架/类库都离不开 npm(Node Package Manager)来解决开发中的依赖管理问题。
在软件管理的领域里存在着 "依赖地狱",系统规模越大,所依赖的包可能就越多,你就越有可能在未来的某一天发现自己已经深陷困境之中。
依赖地狱
在依赖高的系统中发布新版本包很有可能产生依赖地狱。
如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。 而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。
当你项目的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。
共识的约定
依赖地狱的解决方案之一,用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。
版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
主版本号.次版本号.修订号 === major.minor.patch
- 主版本号:当你做了不兼容的 API 修改。
- 次版本号:当你做了向下兼容的功能性新增。
- 修订号:当你做了向下兼容的问题修正。
先行版本号及版本编译信息可以加到"主版本号.次版本号.修订号"的后面,作为延伸。
关于npm版本号述词:
- default:default 不是一个官方的术语,它通常指的是当你不指定一个版本号或标签来安装 npm 包时,npm 使用的默认行为。默认情况下,npm 会安装包的最新稳定版本。
- major:major 版本指的是遵循语义化版本控制 (Semantic Versioning) 的主版本号。
- minor:minor 版本是语义化版本控制中的次版本号。
- patch:patch 版本是语义化版本控制中的修订版本号。
- latest:latest 是 npm 包的一个特殊的分发标签(dist-tag),npm 包作者可以用它来标记哪个版本被视为最新版。
- newest:newest 不是 npm 官方的术语,但通常被理解为最新创建的包版本,而不一定是标记为 latest 的版本
注意,latest 不一定意味着是最新创建的版本,而是维护者指定为推荐给大多数用户的版本。
语义化版本控制规范(SemVer)
以下关键词 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解读。
主版本号.次版本号.修订号,将转换为 X.Y.Z来叙述。
-
使用语义化版本控制的软件必须(MUST)定义公共 API。该 API 可以在代码中被定义或出现于严谨的文档内。无论何种形式都应该力求精确且完整。
-
标准的版本号必须(MUST)采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止(MUST NOT)在数字前方补零。每个元素必须(MUST)以数值来递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。
-
标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容。任何修改都必须(MUST)以新版本发行。
-
主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。更不利于用于生产。
-
1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。
-
修订号 Z(x.y.Z | x > 0)必须(MUST)在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。
-
次版本号 Y(x.Y.z | x > 0)必须(MUST)在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须(MUST)递增。也可以(MAY)在内部程序有大量新功能或改进被加入时递增,其中可以(MAY)包括修订级别的改变。每当 Y 递增时,Z必须(MUST)归零。
-
主版本号 X(X.y.z | X > 0)必须(MUST)在有任何不兼容的修改被加入公共 API 时递增。其中可以(MAY)包括次版本号及修订级别的改变。每当主版本号递增时,y和z必须(MUST)归零。
-
先行版本号可以(MAY)被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。数字型的标识符禁止(MUST NOT)在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。
-
版本编译信息可以(MAY)被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。当判断版本的优先层级时,版本编译信息可(SHOULD)被忽略。因此当两个版本只有在版本编译信息有差别时,属于相同的优先层级。范例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
-
版本的优先层级指的是不同版本在排序时如何比较。
-
- 判断优先层级时,必须(MUST)把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译信息不在这份比较的列表中)。
-
- 由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较。
例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。
-
- 当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。
例如:1.0.0-alpha < 1.0.0。
-
- 有相同主版本号、次版本号及修订号的两个先行版本号,其优先层级必须(MUST)透过由左到右的每个被句点分隔的标识符来比较,直到找到一个差异值后决定:
-
- 只有数字的标识符以数值高低比较。
-
- 有字母或连接号时则逐字以 ASCII 的排序来比较。
-
- 数字的标识符比非数字的标识符优先层级低。
-
- 若开头的标识符都相同时,栏位比较多的先行版本号优先层级比较高。
例如: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。
合法语义化版本的巴科斯范式语法
html
<valid semver> ::= <version core>
| <version core> "-" <pre-release>
| <version core> "+" <build>
| <version core> "-" <pre-release> "+" <build>
<version core> ::= <major> "." <minor> "." <patch>
<major> ::= <numeric identifier>
<minor> ::= <numeric identifier>
<patch> ::= <numeric identifier>
<pre-release> ::= <dot-separated pre-release identifiers>
<dot-separated pre-release identifiers> ::= <pre-release identifier>
| <pre-release identifier> "." <dot-separated pre-release identifiers>
<build> ::= <dot-separated build identifiers>
<dot-separated build identifiers> ::= <build identifier>
| <build identifier> "." <dot-separated build identifiers>
<pre-release identifier> ::= <alphanumeric identifier>
| <numeric identifier>
<build identifier> ::= <alphanumeric identifier>
| <digits>
<alphanumeric identifier> ::= <non-digit>
| <non-digit> <identifier characters>
| <identifier characters> <non-digit>
| <identifier characters> <non-digit> <identifier characters>
<numeric identifier> ::= "0"
| <positive digit>
| <positive digit> <digits>
<identifier characters> ::= <identifier character>
| <identifier character> <identifier characters>
<identifier character> ::= <digit>
| <non-digit>
<non-digit> ::= <letter>
| "-"
<digits> ::= <digit>
| <digit> <digits>
<digit> ::= "0"
| <positive digit>
<positive digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<letter> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
| "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
| "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d"
| "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n"
| "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x"
案例解析
以上的内容就是语义化版本的共识规范,读到这里你可能还是有些不明确。
没关系,我们一起来看几个例子来消化它。
1.0.0-0.3.7
这是 1.0.0 版本的另一个预发布版本。这里的预发布标签是 0.3.7,它可能表示特定的迭代或开发阶段。
1.0.0-x.7.z.92
这同样是 1.0.0 版本的预发布版本。在这个例子中,预发布标签是 x.7.z.92,其中 x、z 可能是占位符或特定的分支命名,而 7 和 92 可能代表了某种进度或修订次数。
1.0.0-alpha
这表明该版本是 1.0.0 的初步版本(alpha),很可能包含尚未测试或尚不稳定的新功能,不适宜在生产环境中使用。
1.0.0-alpha.1
这是 1.0.0-alpha 版本的后续预发布版本,可能包含对 alpha 版本的修正或改进。.1 表示这是 alpha 版本的第一个迭代。
经过这几个简单的案例,你应该已经有所进一步了解了吧。那让我们再来看几个复杂的版本号来加深一下。
1.0.0-alpha+001
- 1.0.0 代表主版本号、次版本号和修订号。
- alpha 是预发布标签,表明这个版本是一个早期的版本,用于测试和反馈,可能包含未完成的特性或尚未完全测试的改动。
- +001 是构建元数据。这可能表示构建编号或日期,或者是其他与构建过程相关的信息。构建元数据不改变版本的优先级,因此 1.0.0-alpha+001 和 1.0.0-alpha 被视为同一优先级。
1.0.0+20130313144700
- 1.0.0 仍然代表主版本号、次版本号和修订号。
- +20130313144700 是构建元数据,看起来像是一个时间戳,表示版本构建的日期和时间。这里的时间戳可能表示 2013 年 3 月 13 日下午 2 点 47 分 00 秒。同样,这些信息不影响版本的优先顺序。
1.0.0-beta+exp.sha.5114f85
- 1.0.0 是基础的版本号。
- beta 是预发布标签,表明这个版本在 alpha 之后,但在正式发布之前,通常包含更稳定的特性集合。
- +exp.sha.5114f85 是构建元数据,可能表示这个构建是一个实验性(exp)的版本,sha 指向一种版本控制标识(像是一个 Git 提交的 SHA-1 散列),而 5114f85 可能是具体散列值的前缀。这提供了关于构建源代码状态的额外信息,但同样不会影响版本的优先级排序。
在 Git 中,每次提交都有一个唯一的 SHA-1 散列值,通常表示为 40 个十六进制字符的字符串。在这个上下文中,7d9b8ff 可能是特定提交的散列值的前七位,这是 Git 默认用来表示提交的简短形式。
随堂测验
当你阅读完上述内容你应该已经对语义化版本的规范有了大致的了解。那让我们做一个简单的小测试。
请对以下版本号进行语义化解析:
sh
0.1.2
1.0.0-beta
2.5.0-alpha.1
3.0.0-rc.2
1.2.3-beta+exp.sha.1a2b3c
4.3.5+20210415
10.20.30-rc.1+build.1234
0.0.1-alpha+001
1.0.0-alpha.beta
2.4.6-0.3.7+exp.sha.5114f85
6.7.8-beta.2+nightly.20210415
3.2.1-x.7.z.92
5.0.0+0.sha.3e7f261
8.3.7+build.11.e0f985a
12.0.0-rc.5+build.7d9b8ff
0.10.0-dev.1+12345
7.4.2-prerelease+abc123
9.8.7+2023.01.25.08.30
11.22.33-beta+exp.sha.e4d909c
13.37.42+build.final
归纳
作为一位负责任的开发者,你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的,我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的时间及烦恼。
感谢(题外话)🌸
本节内容参考了《Semantic Versioning 2.0.0》
本节内容参考了 《RFC 2119》