语义化版本控制规范 SemVer

SemVer(Semantic Versioning,语义化版本控制)是Github起草的一个语义化版本号管理模块,它实现了版本号的解析和比较,规范版本号的格式。它解决了依赖地狱的问题。

基本规则

语义化版本控制,顾名思义,就是让版本号更具有语义,可以传达出关于软件本身的一些重要信息而不只是简单的一串数字。
版本格式:主版本号.次版本号.修订号。
每个部分都为整数(>=0),按照递增的规则改变。
版本号递增规则:

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

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

  • 先行版本号由首位的连接号”-“、标识符号(由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。先行版的优先级低于相关联的标准版本。
  • 版本编译信息由首位的一个加号和一连串以句点分隔的标识符号(由ASCII码的英文数字和连接号标识符[0-9A-Za-z-]组成)组成。如1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。判断版本优先层级时,版本编译信息可以被忽略。

    如何比较版本高低

    判断优先层级时,必须把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较。由左到右依次比较每个标识符号,第一个差异值用来决定优先层级(其中字母连接号以ASCII排序进行比较、其他都相同时栏位多的先行版本号优先级较高)。如:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。

    范围规则

  • <、<=、>、>=、=:指定版本范围,甚至可以通过||组合多个比较器。比如:

    • =1.2.7 <1.3.0中包括1.2.7、1.2.8、1.2.99等等,但不包括1.2.6、1.3.0 或 1.1.0等等;
    • 1.2.7 || >=1.2.9 <2.0.0中包括1.2.7、1.2.9、1.4.6等等,但不包括1.2.8或2.0.0等等。
  • -:连字符表示版本号范围,表示的是一个闭区间。比如:
    • 1.2.3 - 2.3.4 相当于 >=1.2.3 <=2.3.4。
  • x、X、:可以替代[主版本号.次版本号.修订号]三段中任意一段,表示该位置版本号没有限制;另外缺省三段中任意一段与用x、X或替换该段效果相同。比如:
      • 相当于 >=0.0.0,表示任何版本号;
    • 1.X 相当于 >=1.0.0 <2.0.0,匹配到主版本号;
    • 1.2.* 相当于 >=1.2.0 <1.3.0,匹配到主版本号和次版本号;
    • “”(空字符串) 相当于 * ,即相当于 >=0.0.0;
    • 1 相当于 1.x.x,即相当于 >=1.0.0 <2.0.0;
    • 1.2 相当于 1.2.x,即相当于 >=1.2.0 <1.3.0。
  • ~:允许小版本迭代,具体规则:例子:
    • 如果有缺省值,缺省部分任意迭代;
    • 如果没有缺省值,只允许补丁即修订号的迭代
    • ~1.2.3:>=1.2.3 <1.3.0
    • ~1.2:>=1.2.0 < 1.3.0(相当于1.2.x)
    • ~1:>=1.0.0 <2.0.0(相当于1.x)
    • ~0.2.3:>=0.2.3 <0.3.0
    • ~0.2:>=0.2.0 <0.3.0(相当于0.2.x)
    • ~0:>=0.0.0 <1.0.0(相当于0.x)
    • ~1.2.3-beta.2:>=1.2.3-beta.2 <1.3.0【注意,在1.2.3版本中,允许使用大于等于beta.2的先行版本号,而除1.2.3之外的版本号不允许使用先行版本号,所以此处1.2.3-beta.4是允许的,而1.2.4-beta.2是不允许的】
  • ^:允许大版本迭代,具体规则:例子:
    • 允许从左到右的第一段不为0那一版本位+1迭代(左闭右开);
    • 如果有缺省值,且缺省值之前没有不为0的版本位,则允许缺省值的前一位版本+1迭代
    • ^1.2.3:>=1.2.3 <2.0.0
    • ^0.2.3:>=0.2.3 <0.3.0
    • ^0.0.3:>=0.0.3 <0.0.4
    • ^1.2.x:>=1.2.0 <2.0.0
    • ^0.0.x:>=0.0.0 <0.1.0
    • ^0.0:>=0.0.0 <0.1.0
    • ^1.x:>=1.0.0 <2.0.0
    • ^0.x:>=0.0.0 <1.0.0
    • ^1.2.3-beta.2:>=1.2.3-beta.2 <2.0.0【注意,在1.2.3版本中,允许使用大于等于beta.2的先行版本号,而除了1.2.3之外的版本号不允许使用先行版本号,所以此处1.2.3-beta.4是允许的,而1.2.4-beta.2是不允许的】;
    • ^0.0.3-beta:>=0.0.3-beta <0.0.4【同上,此处0.0.3-pr.2是允许的】

      锁定版本

  1. 给定版本,而不是使用版本范围。”autoprefixer”: “7.1.2”缺点:对于一个值得信赖的模块(严格遵循semver原则),开发者通过^来锁定一个模块的大版本,这样在每次重新安装依赖或打包的时候,都能够享受到这个包所有的新增功能和bug修复。现在舍弃了^或~,就只能手动升级包了。
  2. shrinkwrap可以通过执行npm shrinkwrap生成npm-shrinkwrap.json文件来手动锁定版本(按照当前项目node_modules目录内的安装包情况生成稳定的版本号描述)。当执行npm i时,npm会检查在根目录下有没有npm-shrinkwrap.json文件,如果有,npm会使用它(而不是package.json)来确定安装的各个包的版本号信息。
  3. package-lock.json。Default lockfilesShrinkwrap has been a part of npm for a long time, but npm@5 makes lockfiles the default, so all npm installs are now reproducible. The files you get when you install a given version of a package will be the same, every time you install it.We’ve found countless common and time consuming problems can be tied to the “drift” that occurs when different developer environments utilize different package versions. With default lockfiles, this is no longer a problem. You won’t lose time trying to figure out a bug only to learn that it came from people running different versions of a library.npm 5以上版本新增默认的lock文件。lock文件是整个npm包依赖关系树的快照,包含了所有包及其解析的版本,允许在不同机器间的重复构建。相比于shrinkwrap,后者可以实现同样的效果,但package-lock.json表示npm真正支持了locking机制。另外,npm强制package-lock.json不会被发布。npm会在安装包时自动创建 package-lock.json(除非已经有 npm-shrinkwrap.json),并在必要时更新它。运行已经带有 package-lock.json 文件的 npm shrinkwrap 命令将只会对其重命名为 npm-shrinkwrap.json。当两个文件处于某些原因同时存在时,package-lock.json 将被忽略。注意:工作中遇到了一个问题,用了package-lock之后测试环境与本地安装的包不同,这是因为测试环境npm版本是3.#.#,根本不支持package-lock的。
  4. yarn.lockyarn默认提供lock功能(在安装依赖之后,生成一个锁文件[lockfile]来保存你的依赖树中每个模块的精确版本),也就是说,在通过yarn安装了一次依赖后,如果不执行 yarn upgrade,删除后再重新安装的模块的版本不会发生变化、每次发布应用也都将下载完全相同的包代码。优点:
    • yarn比npm安装耗时少;
    • 更简洁的输出(npm的输出信息比较冗长),默认情况下,结合了emoji直观且直接地打印出必要的信息,也提供了一些命令供开发者查询额外的安装信息。

      总结

      在所依赖的包都严格遵循semver时,使用^、~等范围规则是个不错的选择,然而事实是,总有不遵守规则的。这时候为了提高代码稳定性,锁定版本号就十分有必要了。至于使用什么方式来锁定版本,npm package-lock.json 还是 yarn.lock,那就见仁见智了。

      参考