聊聊 npm 的语义化版本(Semver)

冴羽
• 阅读 1911

前言

现在我们要开发一个项目,我们都知道为了方便项目管理,要写一个版本号,那开发的时候初始的版本号是多少呢?是 1.0.0 还是 0.0.1 开始?

如果一个版本号为 X.Y.Z,什么时候是 X 应该加 1,什么时候 Y 应该加 1 ,什么时候 Z 应该加 1,加 1 遵循十进制吗?比如 1.0.9 的下一个版本应该是 1.1.0 吗?

我们经常看到一些项目的版本还带着后缀,比如 React 的 18.0.0-rc.3、 Vue 的 2.7.0-alpha.12,这些又是什么意思呢?这些后缀是固定字段还是可以自定义的呢?

SemVer 规范

实际上,语义化的版本控制并不是一个创新性的想法,即便我们不知道这些,我们也在做类似的事情,但一个明确的规范将会让版本逻辑更清晰的传达给其他开发者。

所以由 Gravatars 创办者兼 GitHub 共同创办者 Tom Preston-Werner 就建立了语义化版本控制的规范, semantic version 简称 semver,于是这个规范就叫做 SemVer 规范,规范地址为:https://semver.org/lang/zh-CN/

我简单讲讲其中的内容,大家也很容易理解:

版本号的格式

标准的版本号必须采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号,英文对应表示为 major、minor、patch,每个元素必须以数值来递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。

主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。

1.0.0 的版本号用于界定公共 API 的形成。

版本号的递增逻辑

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

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

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

简单的总结下就是:

  1. 当有不兼容的 API 更改时,则升级主版本号
  2. 当以向后兼容的方式添加功能时,则升级次版本号
  3. 当进行向后兼容的缺陷修复时,则升级修订号

关于先行版本

先行版本号可以被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须由 ASCII 字母数字和连接号组成,且禁止留白。数字型的标识符禁止在前方补零,举个例子:1.0.0-alpha.6

先行版的优先级低于相关联的标准版本,举个例子 1.0.0-alpha < 1.0.0

被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。

问题回答

其实规范内容并不多,回到开头时的第一个问题:

Q:在 0.y.z 初始开发阶段,我该如何进行版本控制? A:最简单的做法是以 0.1.0 作为你的初始化开发版本,并在后续的每次发行时递增次版本号。

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

alpha beta 和 rc

现在我们知道,先行版本中 - 后的字符是自定义的,我们经常看到一些库的版本会带 alpha、beta 之类的字样,就以 Vue 为例,有 3.0.0-alpha.133.0.0-beta.13.0.0-rc.1,这些表示什么意思呢?

一般来说:

alpha 表示内部测试版,主要给开发和测试找 bug 用,不建议用户下载 beta 表示公开测试版,你可以提前尝试一些功能 rc 是 Release Candidate(候选版本)的缩写,表示该版本功能不再增加,和最终发布版功能一样,有点像预览版,然后可能再改改一些小 bug,就会到正式的版本了。

当然库也可以使用自己的版本逻辑,就比如 React,它还有 next 版和 experimental 版,具体的发布逻辑 React 官网也有写: https://react.docschina.org/docs/release-channels.html

npm 指定版本范围

我们经常在 package.json 文件中看到版本号前出现 ~ ^ 等字符,比如:

{
   "dependencies": {
    "react": "^1.2.3
    "vue": "~1.2.3",
  }
}

这些标识符的作用相信我们多少都知道一点,比如:

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

当然具体的逻辑会更复杂一点,比如:

^只会执行不更改最左边非零数字的更新,所以:

  1. ^0.2.3 相当于 >=1.2.3 <2.0.0

  2. ^0.0.3相当于>=0.0.3 <0.0.4

  3. ^1.2.3-beta.2 相当于 >=1.2.3-beta.2 <2.0.0,这其中 1.2.3-beta.4是可以的,但 1.2.4-beta.2就不行了

    ~如果指定了次版本号,则会只进行修订版本号的更新,如果没有指定,则会进行此版本号的更新,所以:

  4. ~1.2.3 相当于 >=1.2.3 <1.3.0

  5. ~1.2相当于 >=1.2.0 <1.3.0 (相当于 1.2.x)

  6. ~1 相当于 >=1.0.0 <2.0.0 (相当于 1.x)

  7. ~1.2.3-beta.2 相当于 >=1.2.3-beta.2 <1.3.0,这其中 1.2.3-beta.4是可以的,但 1.2.4-beta.2就不行了

除了 ^~,NPM 提供了更多的表示范围版本的方式:https://docs.npmjs.com/cli/v8/configuring-npm/package-json#dependencies

简单举几个示例:

{
  "dependencies": {
    "foo": "1.0.0 - 2.9999.9999",
    "bar": ">=1.0.2 <2.1.2",
    "baz": ">1.0.2 <=2.3.4",
    "qux": "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0",
    "lat": "latest",
  }
}

这其中 latest 表示标签,在默认情况下,npm 使用 latest 标签来标识软件包的当前版本。

对应我们安装包的时候也可以参照这些指定版本安装:

npm install foo@1.2.3
npm install foo@">=0.1.0 <0.2.0"
npm install foo@latest

npm version

NPM 也提供了 npm version 命令可以更新版本号,具体的语法如下:

npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]

其中最主要的就是 majorminorpatch,比如你当下的项目的 package.jsonversion1.0.0

当你执行 npm version major 后,version 会变成 2.0.0 当你执行 npm version minor后,version 会变成 1.1.0 当你执行 npm version patch 后,version 会变成 1.0.1

premajorpreminorprepatch 也是同理:

当你执行 npm version premajor后,version 会变成 2.0.0-0 当你执行 npm version premajor后,version 会变成 1.1.0-0 当你执行 npm version prepatch后,version 会变成 1.0.1-0

而当你执行 npm version prerelease后,version 也会变成 1.0.1-0

prepatchprerelase 的区别在于,prepatch 是增加 patch 号,先行版本号置为 0,prerelase 则是如果没有先行版本号,则跟 prepatch 一样,如果有的话,则会在先行版本号上加 1,举个例子,当你的版本号为 1.0.0-0时,

当你执行 npm version prepatch后,version 会变成 1.0.1-0 而当你执行 npm version prerelease后,version 会变成 1.0.0-1

你也可以在此基础上再添加一个 -m 参数:

npm version patch -m "Upgrade to %s for reasons"

其中 %s 表示新的版本号,npm 会创建一个 commit 信息,就相当于 npm 修改了版本号后,又对文件执行了一句 git commit -am "Upgrade to %s for reasons"

更多 npm version 命令可以参考 npm 官方文档 https://docs.npmjs.com/cli/v8/commands/npm-version

系列文章

冴羽答读者问全目录:https://github.com/mqyqingfeng/Blog

如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

点赞
收藏
评论区
推荐文章
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
React.js中JSX的原理与关键实现
在开始开发之前,我们需要创建一个空项目文件夹。安装1.初始化npm init y2.安装webpack相关依赖npm install webpack webpackcli D3.安装babelloader相关依赖npm install babelloader @babel/core @babel/presetenv D4.
Jacquelyn38 Jacquelyn38
3年前
使用Node.js还可以发邮件?
前言今天,我们给大家开发一个小效果。篇幅比较短,主要给大家展示效果。实战1.首先我们初始化一个Node项目npm init y2.创建一个app.js文件'use strict';const nodemailer  require('nodemaile
kenx kenx
3年前
项目版本管理Git使用详细教程
前言记得刚开始做项目开发的时候都是一个人完成一个项目,单打独斗的开发,也不知道什么是团队开发,没有这个概念,随着工作后来知道公司里项目都是团队开发,这个时候这么多人怎么开发一个项目呢,难道用u盘拷贝嘛,后来知道有这个一个项目版本管理工具前期SVN比较流行后面,开始使用Git这样团队·在做项目开发基于git版本管理就会很轻松快速上手初始化本地仓库项目中
Wesley13 Wesley13
3年前
Android API Level、sdk版本与发行日期 对照表
我们在项目开发过程中,常常需要查看APILevel和sdk版本,来进行一些方法的调用,有时候还需知道对应发布的时间,可以来了解我们最低兼容到的版本是什么时候发布的。在这里贴出来方便以后查看:https://developer.android.com/guide/topics/manifest/usessdkelement.html
Stella981 Stella981
3年前
Liunx版本号码编排惯例
1、内核版本号不代表操作系统整体版本;Linux的内核,以及每一个应用程序、元件、库或者是发行版中的软件包都有自身的版本号。例如你使用的gcc版本号码是2.7.2.3,内核可能是2.2。在安装某个发行版时,所有这些过程得到了简化,因为发行版中包括的每一个软件包都是最新的,例如:RedHat、easyLinux。2、稳定版和开发版任何时候内核都以两
Stella981 Stella981
3年前
IDEA 分享项目到 Git@OSC
前言:在正常的项目开发里面,我们一般是先有项目,然后再建立版本管理的,所以如果是先在Git@OSC(http://my.oschina.net/u/1164642)建立项目然后clone下来,再提交的话,我这种处女座的人总是会很不爽的。。。首先,我们解决的情况是,已经有了一个正在开发的项目,现在我们要把他分享到git@osc上面去。1.第一步,
Easter79 Easter79
3年前
TCP客户端与服务器的实现
为了更容易理解,我们举一个小例子来说明服务器与客户端之间的连接过程。有一个饭店,饭店里有服务员,服务员用于招待客人特别要注意的是:要记住相关函数的各个参数都是什么,什么时候返回SOCKET\_ERROR,什么时候返回INVALID\_SOCKET服务器1include<stdio.h2include<winso
Stella981 Stella981
3年前
Egret 5.3 正式发布,为重度小游戏开发带来新技能
各位开发者好,白鹭引擎团队今天发布2020年最大的一次更新:Egret5.3版本。由于白鹭引擎团队在2019年已经针对部分开发者提供过内部的5.3.x版本,所以本次更新的版本号为5.3.5。根据白鹭引擎2018年以来的规划,版本号第二位为奇数位表示这个版本是抢先体验版而非稳定版,因此我们将在Egret5.3系列版本中相对激进的引入新特性,但是
天航星 天航星
4个月前
Java 比较版本号
在开发中,有时候会遇到比较两个文件的版本号,或者比较多个文件的版本号得出最新版本号。可以用如下代码解决:java/比较版本号@paramversion1版本号1@paramversion2版本号2@return0:版本号1版本号21:版本号1版本号21:版
冴羽
冴羽
Lv1
男 · 淘宝 · 前端工程师
GitHub 26K Star 的博客: https://github.com/mqyqingfeng/Blog
文章
32
粉丝
16
获赞
67