Semantic Versioning (semver) is a versioning scheme that is officially recommended by
npm. It recommends every package to be versioned
in the format MAJOR.MINOR.PATCH
and to follow 3 basic principles of incrementing/bumping (Quoted
from https://semver.org/):
MAJOR
version when you make incompatible API changes;MINOR
version when you add functionality in a backward compatible manner;PATCH
version when you make backward-compatible bug fixes.
Under the scheme of Semantic Versioning, how should we increment the version of a package after updating
- a dependency/optional dependency as specified in
dependencies
/optionalDependencies
, - a dev dependency as specified in
devDependencies
, or - a peer dependency as specified in
peerDependencies
?
For the impatient, feel free to jump to the conclusion.
Dependency / Optional Dependency
In most cases in the npm ecosystem, dependencies and optional dependencies can be seen as part of
internal implementation. Thus, in most cases, merely bumping the version of a dependency or
optional dependency can be seen as internal changes. Thus, unless the package also makes other
relevant changes, merely bumping the version of a dependency usually should only lead to
incrementing PATCH
.
Exceptions
Like almost everything in software engineering, there are exceptions. The following is a couple of
example situations in which we should increment MAJOR
or MINOR
:
- The package wraps an API of the updated dependency, and the wrapped API has changed.
- The package benefits substantially from the dependency update, such as significant performance
improvement. (“Incrementing
MINOR
in case of substantial new functionality or improvements.”)
Dev Dependency
A dev dependency, in most cases, does not impact runtime behaviors. Therefore, merely bumping the version of a dev dependency should not usually even bump the version of the package.
Exceptions
Sometimes, the updated dev dependency involves generating part of what is being packaged and an update leads to a changed package. Here is a couple of examples:
- Consider a package that has the typescript package as a dev dependency. An update of the typescript package may lead to changed type declaration files.
- Consider a package that has the vite package as a dev dependency. An update of the vite package may lead to an updated built package.
Peer Dependency
Patterns of Peer Dependencies
Before we delve into version bumping, let us review the common patterns of peer dependencies.
Consider Package A that specifies a range of versions of Package P as its peer dependency. Package P acts as a host of Package A, and Package A acts as a plugin to Package P. We can see Package P as an environment required by Package A. A dependent of Package A (referred to as Package U) also typically specifies Package P as a dependency/dev dependency. The figure below illustrates this relationship.
For example, an eslint plugin (Package A) typically specifies a range of eslint versions (Package P) as its peer dependency. These versions of eslint are the environment that the eslint plugin requires to run. A package (Package U) that uses this eslint plugin usually specifies both eslint and this eslint plugin as dev dependencies.
Versioning after Updating a Peer Dependency
With the pattern of package relationships around peer dependencies in mind, let us consider the consequence of updating a peer dependency of Package A from the perspective of Package U. Then, based on the consequence, we decide how to bump versions of Package A by following the semver principles at the beginning of this article. We also assume no other API changes to Package A and that Package P also follows semver.
Widening Acceptable Versions
If Package A widens the range of acceptable versions of Package P, e.g., from ^1.0.0
to
^1.0.0 || ^2.0.0
, then we should bump MINOR
since supporting more versions of Package P is a
backward-compatible new feature.
Bumping MAJOR
of a Peer Dependency
If Package A requires a later version of Package P with a MAJOR
version increment, e.g., from
^1.0.0
to ^2.0.0
, then we should bump MAJOR
since the change requires Package U to update
its dependency Package P to a backward-incompatible version.
Bumping MINOR
or PATCH
of a Peer Dependency
What should we do if Package A requires a later version of Package P with a MINOR
or PATCH
version increment, e.g., from ^1.0.0
to ^1.1.0
or from ^1.0.0
to ^1.0.1
?
First, we should understand that this is not a backward-incompatible change in Package A, because
Package U can update Package P as a dependency across MINOR
and PATCH
increments with no
backward compatibility concerns.
Then, we need to figure out whether we should bump MINOR
or PATCH
of Package A. This requires
understanding the rationale behind the principle of incrementing MINOR
in semver. Besides
conveying a message of new APIs or substantial improvements to human readers, this distinction also
implies that it is unsafe to downgrade the dependency if the downgrade decrements MINOR
, while
it is safe if the downgrade decrements PATCH
only.
With this understanding in mind, it is not hard to see that:
- Consider the situation in which Package A requires a later version of Package P with a
MINOR
version increment, e.g., from^1.0.0
to^1.1.0
. We should bumpMINOR
of Package A because it is unsafe for Package U to downgrade Package A beyond this version, which requires downgrading Package P beyond aMINOR
version change. - Consider the situation in which Package A requires a later version of Package P with a
PATCH
version increment, e.g., from^1.0.0
to^1.0.1
. We should bumpPATCH
of Package A because it is safe for Package U to downgrade Package A beyond this version, which only requires downgrading Package P with aPATCH
version decrement.
Conclusion
Type of Updated Dependency | Action | |
---|---|---|
(Optional) Dependency | Bump PATCH ,
unless an exception applies | |
Dev Dependency | Don't bump version, unless an exception applies | |
Peer Dependency | If Peer Dependency | Action on the Package |
Widen Version Range | Bump MINOR | |
Bump MAJOR | Bump MAJOR | |
Bump MINOR | Bump MINOR | |
Bump PATCH | Bump PATCH |