Semver explained - why is there a caret (^) in my package.json?

NAVIGATION

Introducing Semantic Versioning

Giving npm permission to install newer version

Depending on too old version

Updating modules with npm

Reproducible builds

Configuring npm defaults

So you're installing new packages and get your package.json updated as a side effect. Taking a closer look, you notice that there's something in front of the version numbers.

"dependencies": {
    "lodash": "^3.9.2"
}

You're guessing this is some sort of way to widen the range of accepted versions. You'd like to be clear on this one since it leaves you with that unpleasant feeling of uncertainty. You wouldn't like to add a dependency that might break the build for your whole team.

Introducing Semantic Versioning

The prefix character (^) has to do with a version numbering scheme called Semantic Versioning or semver. Semantic Versioning dictates what kind of changes cause the version number to be incremented. Semver uses three-part version number like 3.9.2 and calls these three numbers from left to right as the major, minor and patch numbers.

3 . 9 . 2
major minor patch
Table 1. Semantic Versioning uses three-part version number.

The basic contract for the module maintainer making changes is

  • backward-incompatible change increments the major number
  • new functionality that is backward compatible increments the minor number
  • simple bug fix to existing functionality increments the patch number

For any dependency, the release 1.0.0 is considered the first stable release, and the semver contract does not apply to releases before it.

Let's take lodash version 3.9.2 as a starting point. Major backward incompatible change to, for example, how _.filter() works, would make the next release 4.0.0. A new optional argument to _.map would make the next release 3.10.0. Fixing a bug that wasn't handling certain corner-case in _.find() would make the next release 3.9.3.

Not every module follows Semantic Versioning. A module might use a three-part version number, but increment it as they like. The safest way is to check the module documentation.

Giving npm permission to install newer version

When executing npm install on a fresh checkout without existing node_modules, npm downloads and installs a version that satisfies package.json for each dependency. Instead of specifying the exact version to be installed in package.json, npm allows you to widen the range of accepted versions. You can allow a newer patch level version with tilde (~) and newer minor or patch level version with caret (^).

Symbol Dependency Versions Changes
caret (^) ^3.9.2 3.*.*
  • backwards compatible new functionality
  • old functionality deprecated, but operational
  • large internal refactor
  • bug fix
tilde (~) ~3.9.2 3.9.*
  • bug fix
Table 2. Contract for Semantic Versioning in package.json. Examples based on version 3.9.2.

Depending on too old version

When deciding whether to allow patch or minor level newer versions, it is important to note that old versions usually don't receive patches. The most common branch that gets a bug fix is the latest stable branch, and it is not that often that they are backported for older versions.

Let's say you are using version 1.3.4 of a library with a tilde (~) as your dependency. The library is well maintained and advances quickly. Today the latest version is actually 1.5.1. Then a mission-critical bug is discovered that affects every version to date. The bug gets proper treatment and is soon fixed in version 1.5.2. Now it is likely that there will be no tailored bug fix patch release 1.3.5 for your version, and you end up not getting that bug fix.

Updating modules with npm

When executing npm install on a fresh checkout without existing node_modules, everything works nicely. The package.json file is evaluated, and satisfying versions are installed for each dependency. Things change when you already have node_modules populated. Running npm install will not re-check if there's an even newer version available than you already have installed.

There is a dedicated command npm update for checking and installing newer versions satisfying semver pattern in package.json.

npm update

Or, if you want to update a certain package to the latest version ignoring semantic versioning range, you can use install with the @latest tag.

npm install lodash@latest

Reproducible builds

When you use version number ranges instead of exact versions, there is one important consequence. You can't reproduce the build exactly as it went at a later time. The versions that get installed depend on the time you run npm install. Tomorrow it may install different dependencies than it installs today. This is unsatisfactory for situations where you depend on reproducibility. This is the case for production deployments and public releases.

To make things reproducible, you need to replace version number ranges with exact versions. Npm provides a tool called npm shrinkwrap for doing this.

Configuring npm defaults

The default behavior in npm is to use caret (^) when updating package.json. This default behavior can be configured with

npm config set save-prefix '~'

You can also use --save-exact flag to inform npm that exact match is desired instead of the default save prefix. You can make this latter option default with npm config set save-exact true.

Related articles

Semantic Versioning Cheatsheet

Semantic Versioning Cheatsheet

Learn the difference between caret (^) and tilde (~) in package.json.

Get Cheatsheet

Loading Comments