Ten years ago, I sat down and wrote the first version of what became the Semantic Versioning (SemVer) spec. I was tired of everyone using version numbers in whatever way they wanted and knew we could do better if we all agreed on what each part of a version number meant and how those numbers should change as the project’s code changed.
It worked.
Today, SemVer is one of the world’s most well-known and heavily adopted versioning schemes. Several prominent package ecosystems (like Node’s npm and Rust’s Cargo) built-in SemVer’s concepts from day one. npm alone has more than 1.8 million packages, nearly all of which are versioned with SemVer.
While it’s unrealistic to expect every package to perfectly adhere to SemVer all the time (mistakes do happen), I’ve seen in practice that the vast majority of package developers are quite careful about version number changes and try to follow SemVer in good faith. This can be hard, though, especially when it comes to breaking changes.
In the time before Semantic Versioning, major version number changes often signified large shifts in the architecture, approach, or broad capabilities of the underlying software. In that world, the minor versions would contain breaking changes, especially for products with large API surface areas. Bumping the major version often corresponded to a marketing push to communicate those improvements to the world. This produced a natural outcome: increasing the major version number was a big deal.
Today, in SemVer ecosystems, it’s still a big deal. And I think that’s a problem.
Our collective hesitance to bump the major version of a package is so strong that we sometimes concoct elaborate justifications as to why a breaking change can be included in a minor version release.
“Not many people are using that feature yet; it’ll be fine.”
Or,
“It was clearly intended to be an experimental feature, so we should be able to retroactively mark it as such and then break it in minor, right? Right? RIGHT????”.
Or plainly,
“There isn’t enough to warrant a major version bump; we’re not going to release a major version for some tiny breaking change.”
Either SemVer means something, or it doesn’t. I choose to believe that it does. I’ve seen the benefits to huge package ecosystems and upgrade processes. If we want to live in a world where SemVer improves our collective experience as much as possible, it means we need to believe something new:
Major version numbers are not sacred.
In practice, this means two things:
The first one should be easy. You just have to internalize that major version numbers are not sacred, you’re not going to run out of them, and it’s your duty as a responsible release manager to always indicate that a breaking change is included within. Of course, just because numbers are free doesn’t mean it’s wise to break your API on every release. Each breaking change means manual code modifications for some users of your package. Break the API too frequently, and you’ll risk fatiguing your users by forcing them to dig through your release notes (you do have release notes, right?) and working through the breaking changes to see if any of them are relevant and need action.
The second one is harder. There have been various proposals over the years, a common one being that SemVer should add a 4th part, perhaps called “epoch,” that is the property of the marketing department. So you’d see version 2.3.1.0
instead of just version 3.1.0
, and the 2
indicates that this is the 2nd epochal version corresponding to some marketing event or a larger shift in the library. It’s not a horrible idea, but it does increase the visual complexity of the version number quite a bit and would be challenging to get adopted throughout the various SemVer package ecosystems.
Another idea (and one that doesn’t require SemVer itself to change) is to use a code name or marketing name to associate with a range of major versions. When the name changes, that is the marketing event. For instance, Ubuntu is famous for its wacky animal code names like Trusty Tahr and Bionic Beaver. With a bit of extra discipline, it’s not hard to imagine starting with the letter A and naming each “epoch” with a name starting with the next letter in the alphabet, giving users some sense of how many big iterations the software has been through.
These points are not simply an academic exercise for me. In fact, this post is in part an explanation for why we’re about to release RedwoodJS 2.0 only a bit over a month after releasing 1.0.
Weird, right? See how ingrained it has become to think that major versions should only happen once a year?
So why do we need a 2.0 so soon?
Ok, even if all that’s true, how are we going to reduce the impact of releasing breaking changes so often (perhaps monthly)?
In an effort to drink my own philosophical champagne, I am excited to very soon deliver to you RedwoodJS 2.0, part of the Arapaho Forest epoch. Yep, you guessed it, each epochal version of Redwood will be named after a US national forest, and when we announce a new epochal version (that starts with a B), you can be sure you’ll know about it. In the meantime, you’ll see a number of major versions of Redwood (all with the Arapaho Forest epoch name) without a ton of fanfare surrounding the releases.
I want to live in a world where every breaking change comes gift-wrapped in a major release. I want the promise of SemVer to be fully realized. I think we can get there by rejecting the tyranny of sacred major version numbers. If you feel the same, I hope you’ll join me in embracing this philosophy.