这是用户在 2025-1-14 10:24 为 https://antfu.me/posts/epoch-semver 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Anthony Fu @ antfu.me

Epoch Semantic Versioning
Epoch 语义版本控制

Jan 7 · 8min
1 月 7 日· 8 分钟

If you’ve been following my work in open source, you might have noticed that I have a tendency to stick with zero-major versioning, like v0.x.x. For instance, as of writing this post, the latest version of UnoCSS is v0.65.3, Slidev is v0.50.0, and unplugin-vue-components is v0.28.0. Other projects, such as React Native is on v0.76.5, and sharp is on v0.33.5, also follow this pattern.
如果您一直在关注我在开源方面的工作,您可能已经注意到我倾向于坚持使用零主要版本控制,例如v0.xx 。例如,在撰写本文时,UnoCSS 的最新版本是v0.65.3 , Slidev 是v0.50.0unplugin-vue-componentsv0.28.0 。其他项目,例如React Native v0.76.5 ,sharp v0.33.5 ,也遵循这种模式。

People often assume that a zero-major version indicates that the software is not ready for production. However, all of the projects mentioned here are quite stable and production-ready, used by millions of projects.
人们通常认为零主要版本表明该软件尚未准备好投入生产。然而,这里提到的所有项目都非常稳定并且可以投入生产,已被数百万个项目使用。

Why? - I bet that’s your question reading this.
为什么? - 我敢打赌这就是你读这篇文章时的问题。

Versioning   版本控制

Version numbers act as snapshots of our codebase, helping us communicate changes effectively. For instance, we can say "it works in v1.3.2, but not in v1.3.3, there might be a regression." This makes it easier for maintainers to locate bugs by comparing the differences between these versions. A version is essentially a marker, a seal of the codebase at a specific point in time.
版本号充当代码库的快照,帮助我们有效地传达更改。例如,我们可以说“它在 v1.3.2 中有效,但在 v1.3.3 中无效,可能会出现回归”。这使得维护人员可以更轻松地通过比较这些版本之间的差异来定位错误。版本本质上是一个标记,是代码库在特定时间点的密封。

However, code is complex, and every change involves trade-offs. Describing how a change affects the code can be tricky even with natural language. A version number alone can’t capture all the nuances of a release. That’s why we have changelogs, release notes, and commit messages to provide more context.
然而,代码很复杂,每次更改都涉及权衡。即使使用自然语言来描述更改如何影响代码也可能很棘手。仅凭版本号并不能体现版本的所有细微差别。这就是为什么我们有变更日志、发行说明和提交消息来提供更多上下文。

I see versioning as a way to communicate changes to users — a contract between the library maintainers and the users to ensure compatibility and stability during upgrades. As a user, you can’t always tell what’s changed between v2.3.4 and v2.3.5 without checking the changelog. But by looking at the numbers, you can infer that it’s a patch release meant to fix bugs, which should be safe to upgrade. This ability to understand changes just by looking at the version number is possible because both the library maintainer and the users agree on the versioning scheme.
我将版本控制视为向用户传达更改的一种方式 - 库维护者和用户之间的合同,以确保升级期间的兼容性和稳定性。作为用户,如果不检查变更日志,您无法总是知道v2.3.4v2.3.5之间发生了什么变化。但通过查看数字,您可以推断这是一个旨在修复错误的补丁版本,应该可以安全升级。这种仅通过查看版本号来了解更改的能力是可能的,因为库维护者和用户都同意版本控制方案。

Since versioning is only a contract, and could be interpreted differently to each specific project, you shouldn’t blindly trust it. It serves as an indication to help you decide when to take a closer look at the changelog and be cautious about upgrading. But it’s not a guarantee that everything will work as expected, as every change might introduce behavior changes, whether it’s intended or not.
由于版本控制只是一个合同,并且可以对每个特定项目进行不同的解释,因此您不应该盲目信任它。它可以作为指示,帮助您决定何时仔细查看变更日志并谨慎升级。但这并不能保证一切都会按预期进行,因为每次更改都可能会引入行为变化,无论是有意还是无意。

Semantic Versioning   语义版本控制

In the JavaScript ecosystem, especially for packages published on npm, we follow a convention known as Semantic Versioning, or SemVer for short. A SemVer version number consists of three parts: MAJOR.MINOR.PATCH. The rules are straightforward:
在 JavaScript 生态系统中,特别是对于在 npm 上发布的包,我们遵循称为语义版本控制(Semantic Versioning)或简称 SemVer 的约定。 SemVer 版本号由三部分组成: MAJOR.MINOR.PATCH 。规则很简单:

  • MAJOR: Increment when you make incompatible API changes.
    MAJOR :当您进行不兼容的 API 更改时递增。
  • MINOR: Increment when you add functionality in a backwards-compatible manner.
    MINOR :当您以向后兼容的方式添加功能时递增。
  • PATCH: Increment when you make backwards-compatible bug fixes.
    PATCH :当您进行向后兼容的错误修复时增加。

Package managers we use, like npm, pnpm, and yarn, all operate under the assumption that every package on npm adheres to SemVer. When you or a package specifies a dependency with a version range, such as ^1.2.3, it indicates that you are comfortable with upgrading to any version that shares the same major version (1.x.x). In these scenarios, package managers will automatically determine the best version to install based on what is most suitable for your specific project.
我们使用的包管理器,如npmpnpmyarn ,都是在 npm 上的每个包都遵守 SemVer 的假设下运行的。当您或包指定版本范围的依赖项(例如^1.2.3 )时,表示您愿意升级到共享相同主要版本( 1.xx )的任何版本。在这些情况下,包管理器将根据最适合您的特定项目的版本自动确定要安装的最佳版本。

This convention works well technically. If a package releases a new major version v2.0.0, your package manager won’t install it if your specified range is ^1.2.3. This prevents unexpected breaking changes from affecting your project until you manually update the version range.
这个约定在技术上运作良好。如果某个软件包发布了新的主要版本v2.0.0 ,并且您指定的范围是^1.2.3 ,那么您的软件包管理器将不会安装它。这可以防止意外的重大更改影响您的项目,直到您手动更新版本范围。

However, humans perceive numbers on a logarithmic scale. We tend to see v2.0 to v3.0 as a huge, groundbreaking change, while v125.0 to v126.0 seems a lot more trivial, even though both indicate incompatible API changes in SemVer. This perception can make maintainers hesitant to bump the major version for minor breaking changes, leading to the accumulation of many breaking changes in a single major release, making upgrades harder for users. Conversely, with something like v125.0, it becomes difficult to convey the significance of a major change, as the jump to v126.0 appears minor.
然而,人类以对数尺度感知数字。我们倾向于将v2.0v3.0视为一个巨大的、突破性的变化,而v125.0v126.0则显得微不足道,尽管两者都表明 SemVer 中的 API 变化不兼容。这种看法可能会让维护者犹豫是否要针对较小的重大更改升级主要版本,从而导致在单个主要版本中累积许多重大更改,从而使用户升级变得更加困难。相反,对于像v125.0这样的东西,很难传达重大变化的重要性,因为跳转到v126.0看起来很小。

Dominik Dorfmeister had a great talk about API Design, which mentions an interesting inequality that descripting this: "Breaking Changes !== Marketing Event"
Dominik Dorfmeister做了一次关于 API 设计的精彩演讲,其中提到了一个有趣的不等式来描述这一点: “重大更改!== 营销事件”

Progressive   进步

I am a strong believer in the principle of progressiveness. Rather than making a giant leap to a significantly higher stage all at once, progressiveness allows users to adopt changes gradually at their own pace. It provides opportunities to pause and assess, making it easier to understand the impact of each change.
我坚信进步原则。渐进性不是一下子飞跃到一个更高的阶段,而是让用户按照自己的节奏逐渐采用变化。它提供了暂停和评估的机会,使人们更容易理解每​​个变化的影响。

Progressive as Stairs
Progressive as Stairs - a screenshot of my talk The Progressive Path
渐进式楼梯 - 我的演讲渐进式路径的屏幕截图

I believe we should apply the same principle to versioning. Instead of treating a major version as a massive overhaul, we can break it down into smaller, more manageable updates. For example, rather than releasing v2.0.0 with 10 breaking changes from v1.x, we could distribute these changes across several smaller major releases. This way, we might release v2.0 with 2 breaking changes, followed by v3.0 with 1 breaking change, and so on. This approach makes it easier for users to adopt changes gradually and reduces the risk of overwhelming them with too many changes at once.
我相信我们应该将相同的原则应用于版本控制。我们可以将主要版本分解为更小、更易于管理的更新,而不是将其视为大规模检修。例如,我们可以将这些更改分布在几个较小的主要版本中,而不是发布v2.0.0 ,其中包含v1.x的 10 个重大更改。这样,我们可能会发布包含 2 个重大更改的v2.0 ,然后发布包含 1 个重大更改的v3.0 ,依此类推。这种方法使用户更容易逐步采用更改,并降低了一次因太多更改而不知所措的风险。

Progressive on Breaking Changes
Progressive on Breaking Changes - a screenshot of my talk The Progressive Path
突破性变革的渐进 - 我的演讲《渐进之路》的屏幕截图

Leading Zero Major Versioning
领先的零主要版本

The reason I’ve stuck with v0.x.x is my own unconventional approach to versioning. I prefer to introduce necessary and minor breaking changes early on, making upgrades easier, without causing alarm that typically comes with major version jumps like v2 to v3. Some changes might be "technically" breaking but don’t impact 99.9% of users in practice. (Breaking changes are relative. Even a bug fix can be breaking for those relying on the previous behavior, but that’s another topic for discussion :P).
我坚持使用v0.xx的原因是我自己的非常规版本控制方法。我更喜欢尽早引入必要的和较小的重大更改,使升级更容易,而不会引起通常伴随主要版本跳跃(如v2v3的警报。有些更改可能会“技术上”造成破坏,但实际上不会影响 99.9% 的用户。 (重大变化是相对的。对于那些依赖以前行为的人来说,即使是错误修复也可能会造成破坏,但这是另一个讨论主题:P)。

There’s a special rule in SemVer that states when the leading major version is 0, every minor version bump is considered breaking. I am kind of abusing that rule to workaround the limitation of SemVer. With zero-major versioning, we are effectively abandoning the first number, and merge MINOR and PATCH into a single number (thanks to David Blass for pointing this out):
SemVer 中有一条特殊规则,规定当主要主要版本为0时,每个次要版本碰撞都被视为破坏。我有点滥用这条规则来解决 SemVer 的限制。通过零主版本控制,我们实际上放弃了第一个数字,并将MINORPATCH合并为一个数字(感谢David Blass指出了这一点):

ZERO.MAJOR.{MINOR + PATCH}

Of course, zero-major versioning is not the only solution to be progressive. We can see that tools like Node.js, Vite, Vitest are rolling out major versions in consistent intervals, with a minimal set of breaking changes in each release that are easy to adopt. It would require a lot of effort and extra attentions. Kudos to them!
当然,零主要版本控制并不是唯一渐进的解决方案。我们可以看到,像Node.jsViteVitest这样的工具正在以一致的间隔推出主要版本,并且每个版本中都有少量易于采用的重大更改。这需要大量的努力和额外的关注。向他们致敬!

I have to admit that sticking to zero-major versioning isn’t the best practice. While I aimed for more granular versioning to improve communication, using zero-major versioning has actually limited the ability to convey changes effectively. In reality, I’ve been wasting a valuable part of the versioning scheme due to my peculiar insistence.
我必须承认坚持零主要版本控制并不是最佳实践。虽然我的目标是通过更精细的版本控制来改善沟通,但使用零主要版本控制实际上限制了有效传达更改的能力。事实上,由于我的特殊坚持,我一直在浪费版本控制方案的宝贵部分。

Thus, here, I am proposing to change.
因此,在这里,我建议改变。

Epoch Semantic Versioning
Epoch 语义版本控制

In an ideal world, I would wish SemVer to have 4 numbers: EPOCH.MAJOR.MINOR.PATCH. The EPOCH version is for those big announcements, while MAJOR is for technical incompatible API changes that might not be significant. This way, we can have a more granular way to communicate changes. Similar we also have Romantic Versioning that propose HUMAN.MAJOR.MINOR. But, of course, it’s too late for the entire ecosystem to adopt a new versioning scheme.
在理想的情况下,我希望 SemVer 有 4 个数字: EPOCH.MAJOR.MINOR.PATCHEPOCH版本用于那些重大公告,而MAJOR则用于可能并不重要的技术不兼容的 API 更改。这样,我们就可以采用更精细的方式来传达变更。类似地,我们也有浪漫版本控制,建议HUMAN.MAJOR.MINOR 。但是,当然,对于整个生态系统来说,采用新的版本控制方案已经太晚了。

If we can’t change SemVer, maybe we can at least extend it. I am proposing a new versioning scheme called 🗿 Epoch Semantic Versioning, or Epoch SemVer for short. It’s built on top of the structure of MAJOR.MINOR.PATCH, extend the first number to be the combination of EPOCH and MAJOR. To put a difference between them, we use a third digit to represent EPOCH, which gives MAJOR a range from 0 to 99. This way, it follows the exact same rules as SemVer without requiring any existing tools to change, but provides more granular information to users.
如果我们不能改变 SemVer,也许我们至少可以扩展它。我提出了一种新的版本控制方案,称为🗿 Epoch Semantic Versioning ,简称 Epoch SemVer 。它建立在MAJOR.MINOR.PATCH结构之上,将第一个数字扩展为EPOCHMAJOR的组合。为了区分它们,我们使用第三个数字来表示EPOCH ,它给出了MAJOR范围从 0 到 99。这样,它遵循与 SemVer 完全相同的规则,不需要任何现有工具进行更改,但提供了更精细的信息给用户

The name "Epoch" is inspired by Debian’s versioning scheme.
“Epoch”这个名字的灵感来自于Debian 的版本控制方案

The format is as follows:
格式如下:

{EPOCH * 100 + MAJOR}.MINOR.PATCH
  • EPOCH: Increment when you make significant or groundbreaking changes.
    EPOCH :当您进行重大或突破性更改时增加。
  • MAJOR: Increment when you make minor incompatible API changes.
    MAJOR :当您进行较小的不兼容 API 更改时递增。
  • MINOR: Increment when you add functionality in a backwards-compatible manner.
    MINOR :当您以向后兼容的方式添加功能时递增。
  • PATCH: Increment when you make backwards-compatible bug fixes.
    PATCH :当您进行向后兼容的错误修复时增加。

For example, UnoCSS would transition from v0.65.3 to v65.3.0 (in the case EPOCH is 0). Following SemVer, a patch release would become v65.3.1, and a feature release would be v65.4.0. If we introduced some minor incompatible changes affecting an edge case, we could bump it to v66.0.0 to alert users of potential impacts. In the event of a significant overhaul to the core, we could jump directly to v100.0.0 to signal a new era and make a big announcement. I’d suggest assigning a code name to each non-zero EPOCH to make it more memorable and easier to refer to. This approach provides maintainers with more flexibility to communicate the scale of changes to users effectively.
例如,UnoCSS 将从v0.65.3过渡到v65.3.0 (在EPOCH0情况下)。 SemVer 之后,补丁版本将成为v65.3.1 ,功能版本将成为v65.4.0 。如果我们引入了一些影响边缘情况的微小不兼容更改,我们可以将其升级到v66.0.0以提醒用户潜在的影响。如果对核心进行重大修改,我们可以直接跳转到v100.0.0以标志着新时代的到来并发布重大公告。我建议为每个非零EPOCH分配一个代码名称,以使其更容易记住且更易于引用。这种方法为维护人员提供了更大的灵活性,可以有效地向用户传达更改的规模。

We shouldn’t need to bump EPOCH often. And it’s mostly useful for high-level, end-user-facing libraries or frameworks. For low-level libraries, they might never need to bump EPOCH at all (ZERO-EPOCH is essentially the same as SemVer).
我们不需要经常碰撞EPOCH 。它对于高级、面向最终用户的库或框架最有用。对于低级库,它们可能根本不需要修改EPOCHZERO-EPOCH本质上与 SemVer 相同)。

Of course, I’m not suggesting that everyone should adopt this approach. It’s simply an idea to work around the existing system, and only for those packages with this need. It will be interesting to see how it performs in practice.
当然,我并不是建议大家都采用这种方法。这只是围绕现有系统工作的一个想法,并且仅适用于那些有此需求的软件包。看看它在实践中的表现将会很有趣。

Moving Forward   前进

I plan to adopt Epoch Semantic Versioning in my projects, including UnoCSS, Slidev, and all the plugins I maintain, and ultimately abandon zero-major versioning for stable packages. I hope this new versioning approach will help communicate changes more effectively and provide users with better context when upgrading.
我计划在我的项目中采用 Epoch 语义版本控制,包括 UnoCSS、Slidev 以及我维护的所有插件,并最终放弃稳定包的零主版本控制。我希望这种新的版本控制方法将有助于更有效地传达更改,并在升级时为用户提供更好的上下文。

I’d love to hear your thoughts and feedback on this idea. Feel free to share your comments using the links below!
我很想听听您对此想法的想法和反馈。请随时使用下面的链接分享您的评论!

> comment on bluesky / mastodon / twitter
>bluesky / mastodon / twitter上发表评论

> cd ..
>光盘..
CC BY-NC-SA 4.0 2021-PRESENT © Anthony Fu
CC BY-NC-SA 4.0 2021 年至今 © Anthony Fu