ponyfoo.com

Pragmatic Semantic Versioning

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

These are short-form “thoughts”, in addition to the usual longer-form articles in the blog. The goal is to publish one of these every weekday. I’d love to know what you think. You may send your questions to thoughts@ponyfoo.com. I’ll try to answer them over email and I may publish them here, with your approval. I also write thoughts about the current state of front-end development, and opinions on other people’s articles. You can use the form to the right (near the bottom in mobile) to subscribe via email.

When it comes to semantic versioning, the definition usually depends on who you ask. Everyone has their opinion on what constitutes a patch, what makes a minor, and what warrants a major version bump. In this article, I share my views. Keep in mind this is my opinion and it’s mostly constrained to the sort of software I release: open-source modules that are usually best served with a web browser.

I made the clarification above because in other sorts of software, enterprise grade stuff, you might want to be more “strict” about releases. Think Node.js or io.js. For the majority of us, though, this is not the case.

The consensual definition of semver more or less lies in the following bullet points.

  • patch whenever you release bug fixes that don’t affect the public API in the slightest
  • minor whenever you make a change that can potentially affect the public API, regardless of it being a bug fix or a new feature
  • major whenever you make a change that breaks expectations in the public API, such as removing an API touchpoint, changing its inputs, or its outputs

I prefer to take a pragmatic approach to versioning, as the above is usually a hassle. Bug fixes rarely break consumer code in practice, but almost always fall in the minor category. Consider a bug fix that someone else was using as a crutch and expected it to be a “feature”. That’s a minor because it breaks an expectation, even though it was a measly bug fix, and even though it wasn’t documented, and even though (chances are) nobody actually relied on it.

My approach is to consider these bug fixes that may or may not affect functionality a patch, rather than a minor. This brings me to point number two in this thought.

Getting Rid of Hats

Hats (and tildes) in package.json must die. Die in a fire. Die an ugly death where you burn them so they don’t come back as undead white walkers to frostbite you into an ugly death. Becoming one of them, in turn, and devouring your loved ones.

I’m of course talking about ^ and ~, but also about similarly dangerous operators like >= and *. Why would you take something as precious as npm's immutability and throw it under the bus is beyond me – yet it’s the default configuration value for npm.

After all the hard work that was put into (and is being put into) npm to fix dependency hell, using the mutable dependency operators ask too much from the community. Not everyone is going to be as gentle as you might think. I have had many dependencies break on my face after doing something innocuous like rm -rf node_modules ; npm i, and this shouldn’t be something to worry about. Your ability to deploy safely shouldn’t be lingering just on tests that make sure your dependencies didn’t break overnight.

There is no good reason why dependencies should be able to break overnight. I’ll take my safe deployments over your “minor bug fixes with chances of wreaking havoc” forecasts any day!

Run the following command. This madness ends today!

npm set save-exact true

Have any questions or thoughts you’d like me to write about? Send an email to thoughts@ponyfoo.com. Remember to subscribe if you got this far!

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (7)

Gleb Bahmutov wrote

So much more can be done with semantic versioning, but using exact version numbers is a good start. If you have an existing project, you can clean up all fuzzy characters (plus other stuff) and keep it clean in the future using a grunt plugin grunt-nice-package

For more details on updating dependencies, semantic release and not breaking projects that depend on your module, see my presentation slides http://slides.com/bahmutov/self-improving-software-frontend-camp#/ and read the blog posts

Brian Miller wrote

For development environments, I actually prefer the default settings. I’m going to want to keep my dependencies updated frequently, so I don’t fall too far behind. It’s easier for me to deal with breaking changes, whether they’re intentional or not, closer to when they happen.

For production releases, that’s a different story. And, that’s what npm shrinkwrap is for.

Nicolas Bevacqua wrote

If you use hats or similar accessories you rarely actually notice when a dependency changes. We need a better mechanism to update dependencies knowingly. Even if you use them in development for a few minutes and deem it that there’s no regressions at all, you can’t be sure unless you have a disparate level of test coverage.

Mutable dependencies are terrible. Regardless of how you work around them. We need a better mechanism to update dependencies, better changelogs, and some way to read changelogs as we update the dependencies. That’d make it actually pretty easy to upgrade, if we had the changelog side-by-side while updating each dependency.

Gleb Bahmutov wrote

You can knowingly upgrade dependencies using next-update

Gleb Bahmutov wrote

Sorry, wrong link.

To update your dependencies, use next-update. To test if your current code would break dependents after releasing new version, use [dont-break(https://github.com/bahmutov/dont-break)

Ahmed El Gabri wrote

@Brian Miller you can have a .npmrc with save-exact=true in your project, then npm install will always respect this when you run it inside the project but outside you can still have everything as default.

@Nicolas Bevacqua save-exact=true will not completely solve your problem because now the problem will be in the dependencies of your dependencies which even harder to track. And It did happen to me before and it was awful! unless everyone in the chain is locking versions this is not really effective and you will still need to do shrinkwrap.

Nicolas Bevacqua wrote

Ahmed, you’re absolutely right. Check out this article when it gets published tomorrow morning!