<onWebFocus />

Knowledge is only real when shared.



July 19, 2021

Versioning package releases.

Version Check

Is the version valid?

Version is valid.

Does the version match the range?

Version is in range.

When publishing software to be used by other developers there exists a commonly used standard. The goal of this standard called semver is to inform the user what kind of changes have been applied and how a jump to a new version might impact the integration of the software.

When you develop for the web using various popular open source libraries especially from npm these will almost without exception follow this approach. You don't need this apprach when creating a website or an app. npm and it's configuration file package.json encourage this approach and so you often see this apprach being used by people without a need or knowledge of how it works.

  "name": "my-app",
  "version": "1.0.0",

Above you see the output of npm init which encourages semantic versioning.


Depending on which digit of the version is increased it's a different type of release. When a higher-order digit is increased the ones below are reset to zero. In the following examples we regard the software being released as some kind of a black box with only an interface exposed to the user.


The last number is called a patch release. Examples would be 1.0.0 → 1.0.1 or 12.3.45 → 12.3.46.

Nowadays this would be called a bug fix. A change can be considered a patch when it's an improvement for every user. This could for example be functionality that worked differently from the description and the intention. Under no circumstance should updating a patch version cause issues for any users of the software.

export const multiply = (first: number, second: number) => {
-  return first + second
+  return first * second

Also, patches should not have an effect on overall perforance or size of the application and upgrading is encouraged in almost every case.


The middle number is called a minor release. Examples would be 1.0.0 → 1.1.0 or 0.123.4 → 0.124.0.

Minor releases add new functionality while keeping compatibility with the existing interface.

export const multiply = (first: number, second: number, options?: { verbose: boolean }) => {
+  const result = first * second
+  if (verbose) {
+    console.log(`${first} * ${second} = ${result}`)
+  }
  return first * second

Minor changes often add configuration options requested by users or whole chunks of new functionality which many users of the software probably find useful.


The first and most important number is called a major release. Examples would be 1.0.0 → 2.0.0 or 17.3.2 → 18.0.0.

This type of release is used for so called breaking changes. A breaking change is a change in the interface or the internal implementation which can cause the software to break for some potential users.

- export const multiply = (first: number, second: number) => {
+ export const multiplyNumbers = (first: number, second: number) => {
    return first * second

The above code shows a change in the interface where users whose code relies on multiply will have to update it to use multiplyNumbers instead.

export const multiply = (first: number, second: number) => {
-  return first * second
+  return first * second * first

Here the implementation is changed in a way that will result in a different output and is likely to break the code of some users. Such a change would have to be released as a Major release along with a CHANGELOG mentioning the change and a change in the documentation describing the new implementation.


Since patch or minor releases won't break the software. You can set ranges to allow for the newest version still in range to be installed in the future.

When such a range is used new installs in the future will automatically have updates installed and the installation is likely to match the features mentioned in the current documentation and already fixed bugs won't appear.

npm follows this logic by default and will add a ^ in front of the current version when you install a package the regular way.

dist Tags

Package versions can also be assigned with identifiers called dist tags. The latest tag should always be available and refers to the latest stable version. It can be used like a version range to specify the version in package.json. For example npm install react@next will install the pre-release version of react.


If you need prereleases for a certain version that should be released ahead of the actual version an additional identifier can be added after a dash. For example 1.0.0-beta to first publish a beta release. Note that these suffixed versions won't be matched by ranges.

Differences below 1.0.0

Release 1.0.0 indicates that the software is now ready for production usage and mass adoption. Below 1.0.0 it all moves to the previous place. Since the first digit is not available for breaking changes the second 0.X.0 will now count as a breaking change and the last one 0.0.X is used for both minor and patch releases. Because there's a maximum of three digits patch releses will be combined with minor releases into the third digit.

Installing a package below 1.0.0 the regular way might very well break installations or deployments in the future. Since it's not yet ready for production usage and is using a different digit for breaking changes the default ^ range added will also install the most recent possibly breaking change.

This travesty can either be avoided by only using packages above 1.0.0 or manually replacing the default range with a~ in front of it.

Historical Mishaps

In reality versioning is done by humans and not everybody is aware of the standard or follows it very closely. Here are a few historical examples of bad versioning.


The first version of the most popular frontend framework React 0.8.0 was released in December 2013 after taking over the name from another package now called autoflow. However, the first release to receive significant downloads was 0.13.3 released in May 2015. This was followed by 0.14.0 a few months later. At that time React was already very popular and I've personally used it at work for a big website. Basically, React had already missed the oportunity to make the jump to 1.0.0 and everybody was referring to the minor releases as if they were major releases receiving blog posts dedicated to the new features and how to upgrade. Probably along that line of thinking they decided to deviate from the standard and jump directly to 15.0.0.


Another popular frontend framework called Vue did everything right and moved from 0.12.16 to 1.0.0 in October 2015. However, their version 2.X gained a lot of popularity. While the following version 3.0.0 includes a ton of breaking changes where it's almost impossible for many applications written in version 2 to be upgraded with a reasonable amount of effort. So, they basically decided to treat the new version 3 as a totally different plugin, but still sharing the same name in npm.

React Native

Currently at 0.64.2 this library to develop native mobile applications is apparently still in active development. In reality it has of course been used in production for many years.

Reality Check

The decision whether a small bugfix or change due to a feature might have an impact on some users can be very hard depending on the package at hand. Personally, I found it works quite well with the popular Open Source packages around. Still, it makes sense to run tests after updating or when reinstalling after some time has passed. When you release a website or an app with many users that will only receive maintenance updates by developers not so familiar with the software it's best to remove the ranges to prevent these kinds of small overlooked things from causing issues.