Introduction to Version Ranges
If you’ve ever had a look at your project’s package manifest file, chances are you’ve had exposure to version ranges. In a Node.js project, that package manifest file is called package.json, and usually looks like this:
This little version number with the caret before it is called a version range. In simple terms, it’s a mechanism for locking a package to a flexible amount of versions within a certain range. When you write ^1.4.6
, you can say it out loud as “compatible with version 1.4.6 of this package”. We’re going to go into more detail about this later — for now, just know that version ranges are very powerful tools to make sure your dependencies don’t break.
Why should I use version ranges?
Version ranges are a measure designed to give you stability. They enable you to, for example, cover patch releases which provide important bug fixes, without having to adjust the version number in your package.json file every single time a new version of the dependency is released. Additionally, npm implements a LOT of features which you can use to fine-tune your version ranges in any way you want.
Basic Comparators
Wait, what? Comparators? Comparators are a mix of operators (such as equals, greater than, etc etc) and versions. The most basic ones are as follows:
1.4.0
: Match exactly version 1.4.0, and no other.>1.4.0
: Match the latest version that’s greater than 1.4.0.<1.4.0
: Match the latest version that’s less than 1.4.0.>=1.4.0
: Match the latest version that’s equal or greater than 1.4.0.<=1.4.0
: Match the latest version that’s equal or less than 1.4.0.
These basic comparators can be joined together by ||
. This allows for interesting combinations:
1.4.0 || >= 2.4.0
: Matches the version 1.4.0, but also every version equal or greater than 2.4.0. Doesn’t match 1.3.5 or 2.3.9.1.4.0 || >=1.5.6 <2.4.0
: Matches version 1.4.0, 1.5.7, but not 2.4.0.
Too confusing? Don’t worry, these are more advanced than you will likely ever use them, but they’re a powerful tool to have in your arsenal.
Advanced Constructs
The previously introduced comparators give you all the tools you need to build powerful version ranges, but they’re a bit too verbose. For that reason, npm provides you with many other methods to make writing ranges a bit easier. All of these are internally converted into simple comparators.
1.4.0–1.5.2
: Match every version from 1.4.0 to 1.5.2.1.4.x
: Match 1.4.0, 1.4.1, 1.4.2, and so on.1.x.x
: Match every version that begins with a 1. You can also write this as1.x
.
One of the more important and advanced concepts is tilde ranges. These equate to “approximately equal to version X”, and can look like this:
~1.4.2
: Matches from 1.4.2 all the way up to the last version in the 1.4.x line.~1.4
: Same as above. You can also write this as1.4.x
.~1
: Matches all versions in the1.x.x
line.
It’s a matter of personal preference whether to use the tilde syntax or the 1.x.x syntax, but Greenkeeper works best with the tilde notation, where you can pronounce ~1.4.2
as “roughly 1.4.2”.
Another advanced concept is caret ranges. They ensure that you don’t pull in breaking changes by accident, especially not from packages that haven’t hit 1.0 yet. The reason for this is that anything before 1.0 is generally considered as not stable. This means breaking changes are more likely to be introduced between, say, 0.2.3 and 0.3.0. Caret changes protect you from that. They’re used like this:
^1.4.0
: Matches all the way from 1.4.0 until 2.0.0.^0.3.4
: Matches from 0.3.4 until the last version in the 0.3.x line.^0.0.3
: Matches 0.0.3 only.
As you may have noticed, when you run npm install my-dependency
, npm adds that dependency and uses a caret range to specify its version. If for whatever reason you would prefer to install the exact versions instead of using a caret range, you can run the following command:
This will be saved to your global .npmrc
file.
How Greenkeeper uses version ranges
Greenkeeper is a tool that keeps your npm dependencies updated. As a part of this process, it uses your package.json file to figure out which dependencies to update. This means that, when a new version of a package you depend on is released, Greenkeeper checks if that new version is still within your version range. If not, it creates a pull request to update that package.
As an example, imagine you have a project that uses Greenkeeper and depends on the foobar1
package. Your version range might be defined as such in your package.json:
Now, the foobar1
maintainer releases a new breaking update (2.0.0) that would fail your tests. Greenkeeper detects that this falls outside of your version range as defined in your package.json and sends a new pull request. Your CI fails because your tests would break if you updated the dependency version, so you adjust your code to work with it, and the PR is merged. Success! And at no point was your software in a broken state.
Tips for working with version ranges
- npm uses caret version ranges by default. You may be tempted to use pinned versions (aka. specifying exact versions in the package.json file), yet this would not only break Greenkeeper’s intended usage, but also many other tools that work with your dependency versions. If you use Greenkeeper with pinned versions, you’ll get a lot of pull requests because it treats every dependency update as being out of your specified version range. This might not be what you want, so be careful!
- If you end up using pinned versions, your package-lock.json is not needed since all versions are exact anyways.
- Greenkeeper will also send you a notice in the form of a GitHub issue if a dependency update that is within your version range fails your CI. More information about this can be found here.
We hope you learned something today. If you have any questions, do not hesitate to get in touch!