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 | npm install xxx |
如果你只想把一个依赖包的安装或更新记录再 pacakge.json
中而不希望记录在 package-lock.json
中,则你可以添加参数来避开更新锁文件:
1 | npm install xxx --no-save # 安装依赖包但不记录在package.json中 |
一个典型的 package-lock.json
文件大概如下所示:
1 | { |
它精确描述了 node_moduels
目录下所有的包的树状依赖结构,每个包的版本号都是完全精确的。 可以看到它还包含一个 integrity
字段,它使用 Subresource Integrity 来验证已安装的软件包是否被改动过,换句话来说,验证包是否已失效。它依旧支持旧版本 NPM 中对包的加密算法 SHA-1,但是以后将默认使用 SHA-512 进行加密
指向特定 URI 的文件的 resolved
字段仍然得到了保留。注意,NPM 现在可以(根据 .npmrc
中的设置)解析机器配置使用的不同仓库,这样的话,与 integrity
字段一起配合,只要签名是匹配的,包的来源并无关紧要
。
值得一提的是,lock
文件精确描述了 node_modules 目录中所列出的目录的物理树, 这与其他包管理器(如 Yarn
)不同。 Yarn
仅以 flatten
格式 描述各个包之间的依赖关系,并依赖于其当前实现来创建目录结构。这意味着如果其内部算法发生变化,结构也会发生变化。如果你想了解更多关于 Yarn
和 NPM 5
之间 lock 文件的区别,请查看 Yarn determinism。
基于锁文件来安装依赖
有了 package-lock.json
后,再执行 npm install
则npm会依据这个锁文件里面的版本描述进行精确安装:
- 它会直接使用
package-lock.json
中描述的包的resolved
字段指向的包地址。 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 | { |
注意,这个scripts钩子是不支持 Yarn
包管理器的。
区别
其实 shrinkwrap
与 package-lock
还是有点区别的,
- NPM 强制该 package-lock.json 不会被发布(即这个文件你无法发布到registry)
- 当项目中有2种lock文件时,NPM将完全忽略
package-lock.json
,只使用npm-shrinkwrap.json
如何升级依赖包
如果仅仅执行 npm install
则npm会按照package-lock.json
或 package.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.json
或 shrinkwrap.json
来实现整个Tree的版本锁定
用Yarn吧
yarn由于其特殊的机制,速度要比npm快一些。Yarn的锁文件是Yarn.lock, 我看到 Vue.js项目使用的是Yarn,说明Yarn已经可以用在生产环境了,由于npm仓库不会过滤 Yarn.lock
,所以我们发布一个库的时候其实可以在 package.json
里规定好要发布的文件,就避免发布锁文件了:
1 | "files": [ |