package-locks
npm 锁文件的解释描述
从概念上讲,"input" 到 npm install
是一个 package.json,而它的 "output" 是一个完整的 node_modules
树: 你声明的依赖的表示。 在理想世界中,npm 会像纯函数一样工作: 相同的 package.json
应该在任何时候产生完全相同的 node_modules
树。 在某些情况下,这确实是真的。 但在许多其他情况下,npm 无法做到这一点。 这有多种原因:
可能使用了不同版本的 npm(或其他包管理器)来安装包,每个包使用的安装算法略有不同。
自上次安装包以来,可能已经发布了直接 semver-range 包的新版本,因此将使用更新的版本。
你的一个依赖的依赖可能已经发布了一个新版本,即使你使用固定的依赖说明符(
1.2.3
而不是^1.2.3
)也会更新你安装的注册表不再可用,或者允许版本变更(与主 npm 注册表不同),并且现在在相同版本号下存在不同版本的包。
例如,考虑包 A:
{"name": "A","version": "0.1.0","dependencies": {"B": "<0.1.0"}}
包 B:
{"name": "B","version": "0.0.1","dependencies": {"C": "<0.1.0"}}
和包 C:
{"name": "C","version": "0.0.1"}
如果这些是注册表中唯一可用的 A、B 和 C 版本,则将安装普通 npm install A
:
A@0.1.0`-- B@0.0.1`-- C@0.0.1
但是,如果 B@0.0.2 已发布,则会安装新的 npm install A
:
A@0.1.0`-- B@0.0.2`-- C@0.0.1
假设新版本没有修改 B 的依赖。 当然,新版本的 B 可以包含新版本的 C 和任意数量的新依赖。 如果不需要这样的更改,A 的作者可以指定对 B@0.0.1 的依赖。 但是,如果 A 的作者和 B 的作者不是同一个人,那么 A 的作者就没有办法在 B 完全没有变化的情况下说他或她不想拉入新发布的 C 版本。
为防止此潜在问题,npm 使用 package-lock.json 或 npm-shrinkwrap.json(如果存在)。 这些文件称为包锁或锁文件。
每当你运行 npm install
时,npm 都会生成或更新你的包锁,它看起来像这样:
{"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"}}}}}
这个文件描述了一个精确的,更重要的是可重现的 node_modules
树。 一旦它存在,任何未来的安装都将基于这个文件,而不是重新计算 package.json 的依赖版本。
包锁的存在会改变安装行为,例如:
包锁描述的模块树再现。 这意味着复制文件中描述的结构,如果可用,则使用 "resolved" 中引用的特定文件,如果没有,则使用 "version" 回退到正常的包解析。
遍历树并以通常的方式安装任何缺少的依赖。
如果 preshrinkwrap
、shrinkwrap
或 postshrinkwrap
在 package.json
的 scripts
属性中,它们将按顺序执行。 preshrinkwrap
和 shrinkwrap
在收缩封装之前执行,postshrinkwrap
在之后执行。 这些脚本适用于 package-lock.json
和 npm-shrinkwrap.json
。 例如在生成的文件上运行一些后处理:
"scripts": {"postshrinkwrap": "json -I -e \"this.myMetadata = $MY_APP_METADATA\""}
使用锁定的包
使用锁定的包与使用任何没有包锁的包没有什么不同: 任何更新 node_modules
和/或 package.json
的依赖的命令都会自动同步现有的锁定文件。 这包括 npm
install
、npm rm
、npm update
等。为防止发生此更新,你可以使用 --no-save
选项完全阻止保存,或使用 --no-shrinkwrap
允许更新 package.json
,同时保持 package-lock.json
或 npm-shrinkwrap.json
不变。
强烈建议你将生成的包锁提交给源代码管理: 这将允许你团队中的任何其他人、你的部署、你的 CI/持续集成以及在你的包源中运行 npm install
的任何其他人获得与你正在开发的完全相同的依赖树。 此外,这些更改的差异是人类可读的,并且会通知你 npm 对你的 node_modules
所做的任何更改,因此你可以注意到是否有任何传递依赖被更新、提升等。
解决锁文件冲突
有时,两个单独的 npm install 会创建包锁,从而导致源代码控制系统中的合并冲突。 从 npm@5.7.0
开始,可以通过手动修复任何 package.json
冲突,然后再次运行 npm install [--package-lock-only]
来解决这些冲突。 npm 将自动为你解决任何冲突,并编写一个合并的包锁,其中包括一个合理树中两个分支的所有依赖。 如果提供了 --package-lock-only
,它会在不修改本地 node_modules/
的情况下执行此操作。
为了让这个过程在 git 上无缝,可以考虑安装 npm-merge-driver
,它会教 git 如何在没有任何用户交互的情况下自己做这件事。 简而言之: $ npx
npm-merge-driver install -g
将允许你执行此操作,甚至可以与 npm 5 的 npm@5.7.0
之前版本一起使用,尽管噪音更大一些。 请注意,如果 package.json
本身发生冲突,你必须手动解决并手动运行 npm install
,即使使用合并驱动程序也是如此。
也可以看看
- https://medium.com/@sdboyer/so-you-want-to-write-a-package-manager-4ae9c17d9527
- package.json
- package-lock.json
- shrinkwrap.json
- npm shrinkwrap