不喜欢广告? 无广告 今天

语义化版本控制 版本号系统:你的 npm 安装所依赖的系统

更新于

语义化版本(Semver)的三个数字是一种协议:主版本号表示重大变更,次版本号表示新增功能,补丁版本号表示错误修复——当您的构建在运行 npm install 后失败时,有九成的情况是有人忽视了这一协议。本文将介绍版本号系统的运作方式,package.json 中 ^ 和 ~ 实际上代表的含义,以及为何必须提交锁定文件。

语义化版本:你的 npm 安装所依赖的编号系统 1
广告 移除?

你的构建失败了。周五还能正常运行。 npm install 周一拉取了更新 react-query@5 现在你一半的钩子都消失了。你面对的堆栈跟踪之前并不存在,而某个地方的变更日志正在积灰。

这是一个语义化版本(semver)的故事。具体来说,这是你的责任。

这三个数字实际上意味着什么

MAJOR.MINOR.PATCH —— 就是这些。三个槽位,三条规则:

  • PATCH (1.2.3 → 1.2.4):修复了错误。你的代码无需更改。你只是获得了更少的错误行为。
  • ——仅修复错误。安全升级,无需 API 变更。 (1.2.3 → 1.3.0):新增了一个功能,且向后兼容。你不需要使用它,但它已经存在。
  • MINOR (1.2.3 → 2.0.0):某些内容发生了变化。一个函数被重命名、移除或改变了签名。旧的 API 已经消失或行为不同。

这三条规则中的关键词是 向后兼容。MINOR 和 PATCH 是承诺:“我们没有破坏你正在使用的任何内容。” MAJOR 是警告:“我们确实破坏了。”

当维护者提升 MAJOR 版本,而你没有注意到,因为你将版本锁定在 package.json 中,且锁文件过时——那是因为你。 ^1.0.0 规范完全按设计运行。

语义化版本的社会契约

语义化版本(semver)是一种约定,而非法律。包可以声称遵循它,然后发布一个带有破坏性变更的 MINOR 版本。当这种情况发生时,就是维护者失信。但当一个包正确地提升 MAJOR 版本以表示破坏性变更,而你盲目地拉取它——那么你就是破坏了你自己构建的稳定性。

这就是变更日志存在的原因。一个条目说明“移除了已弃用的 CHANGELOG.md —— 请使用 v1Api 代替”是维护者履行了他们的责任。不阅读它,就是你忽视了自己的责任。变更日志只需两分钟阅读。它所避免的调试会话,是无法估量的。 v2Api “instead”是维护者在履行其责任时的体现。不阅读它,就是你忽视了自己的责任。变更日志只需两分钟即可阅读。它所避免的调试会话,并不真正存在。

^ 与 ~ —— 实际机制

package.json, ^ (caret) 和 ~ (tilde) 定义了版本范围。它们看起来相似,但行为完全不同。

Caret (^): 允许任何不提升 MAJOR 的版本。当你运行 npm 时,这是 npm 的默认行为。 npm install some-package.

  • ^1.2.3 解析为 >=1.2.3 <2.0.0
  • ^0.2.3 解析为 >=0.2.3 <0.3.0 —— 特例: 0.x 将 MINOR 视为破坏性变更
  • ^0.0.3 解析为 >=0.0.3 <0.0.40.0.x 精确锁定,没有浮动空间

Tilde (~): 仅允许 PATCH 更新,且在指定的 MINOR 范围内。

  • ~1.2.3 解析为 >=1.2.3 <1.3.0
  • ~1.2 解析为 >=1.2.0 <1.3.0 —— 与 ~1.2.0
  • ~1 解析为 >=1.0.0 <2.0.0 —— 等同于 ^1.0.0 在这一点上

版本范围示例

范围它允许的内容精确匹配
1.2.3正好是这个版本仅 1.2.3
^1.2.3任何 MINOR/PATCH ≥ 1.2.31.2.4、1.3.0、1.99.0 —— 不包括 2.0.0
^0.2.3仅在 0.2.x 范围内的 PATCH0.2.4、0.2.99 —— 不包括 0.3.0
~1.2.3仅在 1.2.x 范围内的 PATCH1.2.4、1.2.99 —— 不包括 1.3.0
~1.21.2.x 任意补丁版本1.2.0, 1.2.1, 1.2.99
>=1.2.3 <2.0.0显式范围与 ^1.2.3 相同的结果
1.2.x1.2 任意补丁版本1.2.0, 1.2.1, 1.2.99
*任何版本npm 感觉今天想安装的任何版本

* 范围是一种“相信我,兄弟”的版本策略。你没有锁定任何版本。如果一个库发布了完全重写的 API,你将在下一次安装时获得它,前提是缓存是干净的。仅在顶层应用中使用,且这些应用不会被其他包依赖——即使如此,也仅当可重复性真的不重要时(实际上很重要)。 v9.0.0 拥有一个完全重写的API,你将在下一次获得它。 npm install 带有干净的缓存。仅在顶层应用程序中使用,且这些应用程序不会被其他包所依赖——即便如此,也只有当你真正不关心可重现性时(实际上,你确实需要关心)才可使用。

预发布标识符

在稳定版本发布前,维护者会为版本打上预发布标签:

  • 1.0.0-alpha.1 —— 早期,不稳定,API 可能仍在变化
  • 1.0.0-beta.2 —— 功能完整,仍在测试中,预期存在一些粗糙边缘
  • 1.0.0-rc.1 —— 发布候选,除非最终测试中出现异常,否则应可投入生产使用

预发布版本排序 低于 稳定版本: 1.0.0-alpha.1 < 1.0.0并且关键的是, ^1.0.0 —— 预发布版本只有在你显式指定范围内才会匹配。这种行为阻止了你意外地选择一个 alpha 版本而本意是跟踪稳定版本。 不是 安装 2.0.0-beta.1 ——预发布版本只有在你明确指定其范围时才会匹配。这种行为可以防止你无意中选择一个alpha版本,而本意是跟踪稳定版本。

如果你消费的包只有预发布版本,请锁定完整的版本字符串: "some-package": "1.0.0-beta.2"。不要在预发布版本上使用 ^~ ,除非你知道维护者会谨慎对待它们——大多数维护者都不会。

在提交前检查范围

在将版本范围锁定在 package.json之前,值得确认你实际同意安装的内容。版本范围计算器 接收一个版本范围和一组候选版本,并显示哪些版本匹配——当你不确定 是否覆盖你需要的特定版本,或在审查 PR 时发现范围有误时非常有用。 ~2.3 covers a specific version you need, or when you’re reviewing a PR and the range looks off.

三种失败模式

大多数与语义化版本相关的构建失败遵循以下三种模式之一:

  1. ^ + MAJOR 版本提升 + 删除了锁文件: 你锁定过 ^1.0.0,维护者发布了 2.0.0,锁文件被删除或从未提交,CI 安装了 2.0.0。解决方法:提交你的锁文件。每个项目都必须如此,没有例外。
  2. * 在你发布的库中: 你是库作者,使用了 * 作为依赖项。每个下游用户都继承了你的通配符。你让他们的依赖图变成了你的问题。解决方法:在你发布的任何内容中使用显式范围。
  3. 没有锁文件的预发布版本: 一个宽松范围拉取了 1.0.0-alpha.3,API 从 alpha.1改变,结果无法工作。解决方法:显式锁定预发布版本,并——说一遍——提交锁文件。

阅读变更日志

当你的依赖树中的任何包发布 MAJOR 版本时,请花两分钟查看变更日志。维护者编写了它,就是为了让你不必从凌晨三点的堆栈跟踪中逆向推断破坏性变更。

如果一个库在 MINOR 版本中发布破坏性变更却没有变更日志——那就是失信行为。提交问题。公开指出。但如果 MAJOR 版本明显存在,迁移指南详细,而你未查看就拉取了它——工具链正是按你告诉它的行为运行的。契约写在了三个数字里。你只是没有读它。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?