npm中的package锁

为什么需要package锁

npm可以按照 package.json 中定义的依赖进行依赖包的安装,所有依赖包以 平级扁平 的方式放置在 node_modules 目录下。

但是由于如下几种原因,在某些情况下 package.json 是无法保证每个人在自己电脑上执行 npm install 后安装的依赖版本都是一致的. 例如:

  • 不同人使用的npm程序版本如果不同,不同npm版本使用的 package.json 解析和安装算法可能有细微差别
  • 如果 pacakge.json 中记录的依赖包的版本是一个 semver-range 的版本号(即是一个版本范围),那么一旦执行 npm i 就会导致这个包更新到更新的版本
  • 就算你依赖了一个固定版本的包(如A 1.2.3),但你依赖的包A可能依赖其他的包B,而A在声明依赖时可能也使用了 semver 命名如 ^1.2.3, 这时如果包B释出了新版,也会导致包B会安装到更新的版本
  • 如果你用的不是官方的 registry 源,可能私有的源会有版本突变,也会导致不同的人安装到不同的依赖包版本。

如果依赖包的版本不一致,会导致开发环境和生产环境产生不一致的行为;或者导致不同的团队成员之间也产生环境差异

npm是如何解决包版本不一致问题呢?

npm使用 package-lock.json 文件来解决上文这个问题,这个文件叫包的锁(package locks) 或 lockfiles

如何生成和更新package锁文件

在新版的npm中,只要你执行 npm install 则自动会生成 package.json 文件;只要你执行普通的安装、更新等可能会修改 package.json 的npm命令,都会自动同步修改 package-lock.json 文件。这些命令比如:

1
2
3
npm install xxx
npm rm xxx
npm update xxx

如果你只想把一个依赖包的安装或更新记录再 pacakge.json 中而不希望记录在 package-lock.json 中,则你可以添加参数来避开更新锁文件:

1
2
npm install xxx --no-save # 安装依赖包但不记录在package.json中 
npm install xxx --no-shrinkwrap # 安装依赖包并记录再 package.json, 但不记录在 package-lock.json 或 npm-shrinkwrap.json

一个典型的 package-lock.json 文件大概如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"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_moduels 目录下所有的包的树状依赖结构,每个包的版本号都是完全精确的。 可以看到它还包含一个 integrity 字段,它使用 Subresource Integrity 来验证已安装的软件包是否被改动过,换句话来说,验证包是否已失效。它依旧支持旧版本 NPM 中对包的加密算法 SHA-1,但是以后将默认使用 SHA-512 进行加密

指向特定 URI 的文件的 resolved 字段仍然得到了保留。注意,NPM 现在可以(根据 .npmrc 中的设置)解析机器配置使用的不同仓库,这样的话,与 integrity 字段一起配合,只要签名是匹配的,包的来源并无关紧要

值得一提的是,lock 文件精确描述了 node_modules 目录中所列出的目录的物理树, 这与其他包管理器(如 Yarn )不同。 Yarn 仅以 flatten 格式 描述各个包之间的依赖关系,并依赖于其当前实现来创建目录结构。这意味着如果其内部算法发生变化,结构也会发生变化。如果你想了解更多关于 YarnNPM 5 之间 lock 文件的区别,请查看 Yarn determinism

基于锁文件来安装依赖

有了 package-lock.json 后,再执行 npm install 则npm会依据这个锁文件里面的版本描述进行精确安装:

  1. 它会直接使用 package-lock.json 中描述的包的 resolved字段指向的包地址。
  2. package-lock里面描述的依赖全部安装完成后,任何缺失的依赖,都会用普通的方式安装

请把package锁提交到版本库

请把你的 package-lock.json 提到你的代码版本仓库,从而让你的团队成员、运维部署人员或CI系统可以在执行 npm install 时安装的依赖版本都是一致的。 而且由于 pacakge-lock.json 是文本文件,大家也可以清楚地看到谁更改了里面哪个依赖,从而更好的定位和发现问题

shrinkwrap

npm还支持 npm-shrinkwrap.json,他也叫锁文件,而且跟 package-lock.json 的功能是完全一样的。

可以通过执行以下命令来生成 npm-shrinkwrap.json

1
npm shrinkwrap

如果项目中已经有 pacakge-lock.json 了,则 NPM 仅仅会把 package-lock.json 重命名为 npm-shrinkwrap.json

另外,在npm的 package.json 中有3个scripts钩子可以使用,分别是: preshrinkwrap, shrinkwrap or postshrinkwrap, 可以在你执行 npm install 之前和之后触发一些逻辑。例如:

1
2
3
4
5
6
7
{
"scripts": {
"preshrinkwrap": "echo preshrinkwrap",
"shrinkwrap": "echo shrinkwrap",
"postshrinkwrap": "echo postshrinkwrap"
},
}

注意,这个scripts钩子是不支持 Yarn 包管理器的。

区别

其实 shrinkwrappackage-lock 还是有点区别的,

  • NPM 强制该 package-lock.json 不会被发布(即这个文件你无法发布到registry)
  • 当项目中有2种lock文件时,NPM将完全忽略 package-lock.json,只使用 npm-shrinkwrap.json

如何升级依赖包

如果仅仅执行 npm install 则npm会按照package-lock.jsonpackage.json文件描述的版本进行升级,这样仅仅会把版本升级到当前记录的主版本号下的最新版。

如果你希望进行大版本升级,则需要亲自指定大版本号才行

1
npm i webpack@4

解决package-lock.json的冲突

如果多人更改导致 package-lock.json 发生冲突,则需要手工解决 package.json 里的冲突,然后重新执行:

1
npm install [--package-lock-only]

带上 --package-lock-only 可以防止不改变本地的 node_modules

自动解决冲突

可以使用这个 npm-merge-driver 的工具

1
npx npm-merge-driver install -g

库项目不要发布锁文件

如果你正在开发一个库(如其他人所依赖的软件包),则应确保 lockfile 不要发布到registry。 由于 package-lock.json 默认就不会发布出去,所以我们要关注的是当你使用 shrinkwrap.json 时请确保不要发布到registry。之所以库项目不要发布 lockfile, 是因为库项目一般是被其他项目依赖的,在不写死的情况下,就可以复用主项目已经加载过的包;而一旦你的库依赖的是死的版本号那么就会造成包的冗余。

而如果你要发布的是一个上线的web应用,在 package.json 中指定一个特定版本依赖是不够的,因为package.json里写明版本号只能确保你的顶层依赖的版本号保持一致,无法保证依赖的依赖也能保持一致;因此你如果希望确保最终用户获得完全相同的依赖关系树,包括其所有子依赖关系,务必要使用 package-lock.jsonshrinkwrap.json 来实现整个Tree的版本锁定

用Yarn吧

yarn由于其特殊的机制,速度要比npm快一些。Yarn的锁文件是Yarn.lock, 我看到 Vue.js项目使用的是Yarn,说明Yarn已经可以用在生产环境了,由于npm仓库不会过滤 Yarn.lock,所以我们发布一个库的时候其实可以在 package.json里规定好要发布的文件,就避免发布锁文件了:

1
2
3
4
5
"files": [
"src",
"dist/*.js",
"types/*.d.ts"
]

refer

npm-package-locks
[译]理解NPM5中的lock文件