语义化版本:主版本号的承诺是「不会静默改变行为」

MAJOR 不是「改动很大」,而是「有破坏性变更」。把重构当主版本号、把破坏性变更藏在次版本号,都会让下游的自动升级变成事故。

1.4.2 → 1.5.0 表示「加了功能,没破坏」。这条约定真正的价值是让下游可以写 ^1.4.2 而不用逐版本审查。它承诺的不是「改动大不大」,而是破坏性变更会不会静默发生。

三个数字的语义

位置 什么时候加 下游可以假设
MAJOR 任何不兼容的改动 需要人工介入
MINOR 向后兼容的新功能 直接升级安全
PATCH 向后兼容的缺陷修复 直接升级安全

关键在「向后兼容」的定义。改一个函数的默认参数值、把 console.log 换掉、调整排序稳定性 —— 这些都很小,但都是破坏性的。

0.x 的特殊性

0.y.z 阶段任何发布都可能是破坏性的。所以 ^0.4.2 在语义上不表达「0.5 也兼容」。

npm 的处理是特例:^0.4.2 实际只允许 0.4.x。这个行为常被误解,值得记住 —— 很多「自动升级没升上去」的原因就在这里。

发布前要问的三个问题

  1. 旧代码在新版本上还能跑吗? 不能 → MAJOR
  2. 有新增的公开 API 吗? 有 → MINOR
  3. 只是内部修复吗? 是 → PATCH

如果答案是「行为变了但接口没变」,那仍然是 MAJOR。运行时行为的可观察变化也算不兼容 —— 恰恰是最容易漏判的一类,因为类型检查抓不到。

预发布与构建元数据

1.5.0-beta.1 里 beta.1 是预发布标识,排序低于 1.5.0。用 < 比较时 1.5.0-beta.1 < 1.5.0 成立。

1.5.0+build.7 里 +build.7 只是元数据,不参与版本比较。用它传递构建信息可以,用它排序会得到意外结果。

lockfile 与 range 是两件事

package.json 里的 range 表达意图,package-lock.json 固定实际解析结果。只提交前者,不同机器会装出不同版本 —— 这是「我本地能跑」的经典来源。

主版本号增一,是在说「你需要读一下迁移说明」。这句话不能随便说,也不能在该说时不说。

← 返回文章列表

评论

…