201 lines
6.8 KiB
Groff
201 lines
6.8 KiB
Groff
.TH "PACKAGE\-LOCKS" "5" "November 2019" "" ""
|
|
.SH "NAME"
|
|
\fBpackage-locks\fR \- An explanation of npm lockfiles
|
|
.SS Description
|
|
.P
|
|
Conceptually, the "input" to npm help \fBinstall\fP is a npm help package\.json, while its
|
|
"output" is a fully\-formed \fBnode_modules\fP tree: a representation of the
|
|
dependencies you declared\. In an ideal world, npm would work like a pure
|
|
function: the same \fBpackage\.json\fP should produce the exact same \fBnode_modules\fP
|
|
tree, any time\. In some cases, this is indeed true\. But in many others, npm is
|
|
unable to do this\. There are multiple reasons for this:
|
|
.RS 0
|
|
.IP \(bu 2
|
|
different versions of npm (or other package managers) may have been used to install a package, each using slightly different installation algorithms\.
|
|
.IP \(bu 2
|
|
a new version of a direct semver\-range package may have been published since the last time your packages were installed, and thus a newer version will be used\.
|
|
.IP \(bu 2
|
|
A dependency of one of your dependencies may have published a new version, which will update even if you used pinned dependency specifiers (\fB1\.2\.3\fP instead of \fB^1\.2\.3\fP)
|
|
.IP \(bu 2
|
|
The registry you installed from is no longer available, or allows mutation of versions (unlike the primary npm registry), and a different version of a package exists under the same version number now\.
|
|
|
|
.RE
|
|
.P
|
|
As an example, consider package A:
|
|
.P
|
|
.RS 2
|
|
.nf
|
|
{
|
|
"name": "A",
|
|
"version": "0\.1\.0",
|
|
"dependencies": {
|
|
"B": "<0\.1\.0"
|
|
}
|
|
}
|
|
.fi
|
|
.RE
|
|
.P
|
|
package B:
|
|
.P
|
|
.RS 2
|
|
.nf
|
|
{
|
|
"name": "B",
|
|
"version": "0\.0\.1",
|
|
"dependencies": {
|
|
"C": "<0\.1\.0"
|
|
}
|
|
}
|
|
.fi
|
|
.RE
|
|
.P
|
|
and package C:
|
|
.P
|
|
.RS 2
|
|
.nf
|
|
{
|
|
"name": "C",
|
|
"version": "0\.0\.1"
|
|
}
|
|
.fi
|
|
.RE
|
|
.P
|
|
If these are the only versions of A, B, and C available in the
|
|
registry, then a normal \fBnpm install A\fP will install:
|
|
.P
|
|
.RS 2
|
|
.nf
|
|
A@0\.1\.0
|
|
`\-\- B@0\.0\.1
|
|
`\-\- C@0\.0\.1
|
|
.fi
|
|
.RE
|
|
.P
|
|
However, if B@0\.0\.2 is published, then a fresh \fBnpm install A\fP will
|
|
install:
|
|
.P
|
|
.RS 2
|
|
.nf
|
|
A@0\.1\.0
|
|
`\-\- B@0\.0\.2
|
|
`\-\- C@0\.0\.1
|
|
.fi
|
|
.RE
|
|
.P
|
|
assuming the new version did not modify B's dependencies\. Of course,
|
|
the new version of B could include a new version of C and any number
|
|
of new dependencies\. If such changes are undesirable, the author of A
|
|
could specify a dependency on B@0\.0\.1\|\. However, if A's author and B's
|
|
author are not the same person, there's no way for A's author to say
|
|
that he or she does not want to pull in newly published versions of C
|
|
when B hasn't changed at all\.
|
|
.P
|
|
To prevent this potential issue, npm uses npm help package\-lock\.json or, if present, npm help npm\-shrinkwrap\.json\. These files are called package locks, or lockfiles\.
|
|
.P
|
|
Whenever you run \fBnpm install\fP, npm generates or updates your package lock,
|
|
which will look something like this:
|
|
.P
|
|
.RS 2
|
|
.nf
|
|
{
|
|
"name": "A",
|
|
"version": "0\.1\.0",
|
|
\.\.\.metadata fields\.\.\.
|
|
"dependencies": {
|
|
"B": {
|
|
"version": "0\.0\.1",
|
|
"resolved": "https://registry\.npmjs\.org/B/\-/B\-0\.0\.1\.tgz",
|
|
"integrity": "sha512\-DeAdb33F+"
|
|
"dependencies": {
|
|
"C": {
|
|
"version": "git://github\.com/org/C\.git#5c380ae319fc4efe9e7f2d9c78b0faa588fd99b4"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.fi
|
|
.RE
|
|
.P
|
|
This file describes an \fIexact\fR, and more importantly \fIreproducible\fR
|
|
\fBnode_modules\fP tree\. Once it's present, any future installation will base its
|
|
work off this file, instead of recalculating dependency versions off
|
|
npm help package\.json\.
|
|
.P
|
|
The presence of a package lock changes the installation behavior such that:
|
|
.RS 0
|
|
.IP 1. 3
|
|
The module tree described by the package lock is reproduced\. This means
|
|
reproducing the structure described in the file, using the specific files
|
|
referenced in "resolved" if available, falling back to normal package resolution
|
|
using "version" if one isn't\.
|
|
.IP 2. 3
|
|
The tree is walked and any missing dependencies are installed in the usual
|
|
fashion\.
|
|
|
|
.RE
|
|
.P
|
|
If \fBpreshrinkwrap\fP, \fBshrinkwrap\fP or \fBpostshrinkwrap\fP are in the \fBscripts\fP
|
|
property of the \fBpackage\.json\fP, they will be executed in order\. \fBpreshrinkwrap\fP
|
|
and \fBshrinkwrap\fP are executed before the shrinkwrap, \fBpostshrinkwrap\fP is
|
|
executed afterwards\. These scripts run for both \fBpackage\-lock\.json\fP and
|
|
\fBnpm\-shrinkwrap\.json\fP\|\. For example to run some postprocessing on the generated
|
|
file:
|
|
.P
|
|
.RS 2
|
|
.nf
|
|
"scripts": {
|
|
"postshrinkwrap": "json \-I \-e \\"this\.myMetadata = $MY_APP_METADATA\\""
|
|
}
|
|
.fi
|
|
.RE
|
|
.SS Using locked packages
|
|
.P
|
|
Using a locked package is no different than using any package without a package
|
|
lock: any commands that update \fBnode_modules\fP and/or \fBpackage\.json\fP\|'s
|
|
dependencies will automatically sync the existing lockfile\. This includes \fBnpm
|
|
install\fP, \fBnpm rm\fP, \fBnpm update\fP, etc\. To prevent this update from happening,
|
|
you can use the \fB\-\-no\-save\fP option to prevent saving altogether, or
|
|
\fB\-\-no\-shrinkwrap\fP to allow \fBpackage\.json\fP to be updated while leaving
|
|
\fBpackage\-lock\.json\fP or \fBnpm\-shrinkwrap\.json\fP intact\.
|
|
.P
|
|
It is highly recommended you commit the generated package lock to source
|
|
control: this will allow anyone else on your team, your deployments, your
|
|
CI/continuous integration, and anyone else who runs \fBnpm install\fP in your
|
|
package source to get the exact same dependency tree that you were developing
|
|
on\. Additionally, the diffs from these changes are human\-readable and will
|
|
inform you of any changes npm has made to your \fBnode_modules\fP, so you can notice
|
|
if any transitive dependencies were updated, hoisted, etc\.
|
|
.SS Resolving lockfile conflicts
|
|
.P
|
|
Occasionally, two separate npm install will create package locks that cause
|
|
merge conflicts in source control systems\. As of \fBnpm@5\.7\.0\fP, these conflicts
|
|
can be resolved by manually fixing any \fBpackage\.json\fP conflicts, and then
|
|
running \fBnpm install [\-\-package\-lock\-only]\fP again\. npm will automatically
|
|
resolve any conflicts for you and write a merged package lock that includes all
|
|
the dependencies from both branches in a reasonable tree\. If
|
|
\fB\-\-package\-lock\-only\fP is provided, it will do this without also modifying your
|
|
local \fBnode_modules/\fP\|\.
|
|
.P
|
|
To make this process seamless on git, consider installing
|
|
\fBnpm\-merge\-driver\fP \fIhttps://npm\.im/npm\-merge\-driver\fR, which will teach git how
|
|
to do this itself without any user interaction\. In short: \fB$ npx
|
|
npm\-merge\-driver install \-g\fP will let you do this, and even works with
|
|
pre\-\fBnpm@5\.7\.0\fP versions of npm 5, albeit a bit more noisily\. Note that if
|
|
\fBpackage\.json\fP itself conflicts, you will have to resolve that by hand and run
|
|
\fBnpm install\fP manually, even with the merge driver\.
|
|
.SS See Also
|
|
.RS 0
|
|
.IP \(bu 2
|
|
https://medium\.com/@sdboyer/so\-you\-want\-to\-write\-a\-package\-manager\-4ae9c17d9527
|
|
.IP \(bu 2
|
|
npm help package\.json
|
|
.IP \(bu 2
|
|
npm help package\-lock\.json
|
|
.IP \(bu 2
|
|
npm help shrinkwrap\.json
|
|
.IP \(bu 2
|
|
npm help shrinkwrap
|
|
|
|
.RE
|