Semantic Versioning Intro
Before getting too deep into npm package specifics, it's worth taking a look at semantic versioning in general. If you're not familiar with semantic versioning (sometimes referred to as "semver"), it is a simple set of rules and requirements that dictate how version numbers are assigned and incremented.
Typically you'll see semantically versioned packages formatted as X.Y.Z
or Major.Minor.Patch
where each period-separated part of the version is a positive integer without leading zeroes. For example, the first production release of any package should be 1.0.0
.
Occasionally you may see slight variations of this standard. For example, packages are versioned with a major version of zero like 0.2.1
. This indicates that a package is in initial development and that the entire API is subject to change. Another common variation of the standard is to append a modifier like alpha
, beta
, or build numbers to the end of a version. Both of these variations are compliant with the official semver standard.
For package creators, semantic versioning is most important when making changes to an existing package. The developer needs to decide what kind of version bump the change requires. Below is a list of scenarios to take into consideration when deciding how to change a package version.
- Bump patch version: Bug fixes not affecting the API
- Bump minor version: Backward compatible API additions/changes
- Bump major version: Backward incompatible API changes
While the standard is relatively straightforward, I think going too deep into the specifics is out of scope for what I'm looking to cover in this post. If you're interested in learning more head over to the semver spec to get a more in-depth understanding of everything it entails.
package.json
Specifics
All packages that are published to the npm repository are required to contain a package.json file. The package.json file serves the following purposes.
- Lists the packages your project depends on
- Specifies versions of a package that your project can use using semantic versioning rules
- Makes your build reproducible, and therefore easier to share with other developers
There are many fields that can be included in the package.json file, but for this post, we are mostly concerned with the "dependencies"
and "devDependencies"
fields. The difference between the two is that devDependencies
are packages that are only needed for local development or testing. Whereas the packages listed in the dependencies
section are expected to be needed in production. To install a dev dependency the --save-dev
flag should be used when running npm install
for the package.
npm install <package_name> --save-dev
Both the dependencies
and devDependencies
sections include package names along with semantic version numbers. If you've seen package.json files before you may have noticed that the exact package version isn't always used when referencing packages. Within the semver spec we can specify package ranges for which the "best" package meeting the range's criteria will be selected for use.
{
"dependencies": {
"foo": "1.0.0 - 2.9999.9999",
"bar": ">=1.0.2 <2.1.2",
"baz": ">1.0.2 <=2.3.4"
}
Getting into node-semver
Now I'll be going through the different options that we have available when specifying package versions in the package.json file. Again these rules are compliant with the semver spec, which is managed in the node-semver package globally installed with npm.
1.2.3
Specify the exact version of a package. If that package doesn't exist, an error will be thrown.>1.2.3
,>=1.2.3
,<1.2.3
,<=1.2.3
The comparison operators work exactly how you'd expect them to. The resolved package will be the next available version compared using the provided operator. The comparisons can also be combined to specify explicit ranges like>=1.2.3 <=3.2.1
.~1.2.3
The tilde range resolves a package that is "approximately equivalent to" the specified version. This range behaves slightly differently depending on whether or not a Minor or Patch version is included in the requested version. For example,~1.2.3
will allow patch-level changes greater than1.2.3
, but less than1.3.0
whereas~1
will allow minor or patch version upgrades up until2.0.0
.^1.2.3
Carat ranges allow patch and minor updates for versions 1.0.0 and above, patch updates for versions 0.X >=0.1.0, and no updates for versions 0.0.X. The updates allowed are based on the left-most zero in the specified version.1.2.x
Allows for patch upgrades only*
or""
will match any version 😱1.2.3-3.2.1
is the same as>=1.2.3 <=3.2.1
1.2.3-3.2.1 || 4.0.0-5.0.0
will pass as long as either of the ranges are met- HTTP, Git URLs, and local paths can also be referenced, but are less commonly used.
For a more hands-on look at semver resolution specifics, check out the semantic versioning calculator which provides a UI to play around with dependency versions and ranges. This is a quick intro into what can be a fairly complicated space. I hope it provides a foundation to learn more if you need to do so!