Side-by-side versions
This hands-on demo uses the lockfile-explorer-demos
environment.
For setup instructions, read the Demos repository article.
Step 1: A diamond dependency
GitHub checkout branch: demo/sbs-1
cd lockfile-explorer-demos
# WARNING: THIS COMMAND WILL DELETE ANY LOCAL CHANGES YOU MADE
git checkout -f -B demo/sbs-1 remotes/origin/demo/sbs-1
rush install
Let's start with a simple side-by-side version, as discussed in the Version conflicts
section. In the above diagram, A
depends on B
and also P
:
{
"name": "@rushstack/a",
"version": "0.0.0",
"private": true,
"dependencies": {
"@rushstack/b": "workspace:*",
"@rushstack/p": "~2.0.1"
}
}
Whereas B
also depends on P
, but using the SemVer range ~2.0.2
instead of ~2.0.1
.
This creates a "diamond dependency". However, because the package manager is able to satisfy both ranges
using P@2.0.3
, it does not create a side-by-side version.👍
The resulting lockfile looks like this:
common/config/rush/pnpm-lock.yaml (shown with unimportant fields omitted)
importers:
../../projects/a:
specifiers:
'@rushstack/b': workspace:*
'@rushstack/p': ~2.0.1
dependencies:
'@rushstack/b': link:../b
'@rushstack/p': 2.0.3
../../projects/b:
specifiers:
'@rushstack/p': ~2.0.2
dependencies:
'@rushstack/p': 2.0.3
packages:
/@rushstack/p/2.0.3:
dev: false
Step 2: Side-by-side versions
GitHub checkout branch: demo/sbs-2
# WARNING: THIS COMMAND WILL DELETE ANY LOCAL CHANGES YOU MADE
git checkout -f -B demo/sbs-2 remotes/origin/demo/sbs-2
rush install
Step 2 adds a third project C
which depends on P: ^1.0.0
, which does produce side-by-side versions.
It's not ideal for different monorepo projects to be using inconsistent version ranges. (What if
in the future A
takes a dependency on C
?). However in this lockfile there's no immediate trouble,
since the two dependency trees are disjoint.
Step 3: Side-by-side version trouble
GitHub checkout branch: demo/sbs-3
# WARNING: THIS COMMAND WILL DELETE ANY LOCAL CHANGES YOU MADE
git checkout -f -B demo/sbs-3 remotes/origin/demo/sbs-3
rush install
Step 3 introduces a version conflict: In the above diagram, A
depends on P: ^2.0.0
however B
is using P: ^1.0.0
. This means that A
's dependency tree now includes two different versions P
.
As discussed in Version conflicts, this might cause performance issues,
such as a performance regression where A
's bundle contains ttwo copies of P
. It could also cause a TypeScript
compile error, due to conflicts between the .d.ts files from P@2.0.3
versus P@1.2.3
. Even if the code compiles,
it could also cause a runtime failure, for example if two instances of a singleton get initialized.
Let's launch Lockfile Explorer and inspect it:
cd lockfile-explorer-demos
lockfile-explorer
The app window will look something like this:
Lockfile Explorer view of demos/sbs-3 branch
On the left pane, you can see the two entries for P@2.0.3
and P@1.2.3
. And the Direct Referrers
pane helps us to trace backwards to understand how these versions were introduced.
The examples on this page all rely on inconsistent version ranges in package.json files. Rush provides a strict setting ensureConsistentVersions specifically designed to avoid these inconsistencies. In these demo branches, that policy has been disabled in rush.json.