npm中有一个package-lock.json
的文件,即npm依赖锁文件,用来描述npm依赖生成的确切树,这样不管你的依赖有何种更新,都会按照这个确切树来安装使用。
不同的包管理工具对应不同的锁文件:
● npm => package-lock.json
● yarn => yarn.lock
● pnpm => pnpm-lock.yaml
整体上大同小异,本文主要以npm为例。
一、semver 版本控制规范
在软件管理的领域里存在着被称作"依赖地狱"的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。
为了解决依赖包的版本混乱问题,制定了这个 semver 规范。
版本格式:【主版本号.次版本号.修订号】,版本号递增规则如下:
- 主版本号:当你做了不兼容的 API 修改,(不兼容更新)
- 次版本号:当你做了向下兼容的功能性新增,(功能新增)
- 修订号:当你做了向下兼容的问题修正。(bug修复)
先行版本号及版本编译信息可以加到"主版本号.次版本号.修订号"的后面,作为延伸。(例如beta、test版本)
(更详细内容参考官网文档:https://semver.org/lang/zh-CN/)
二、安装依赖时的版本规则
npm依赖规则中,还有 >、>=、<、<=、x、*、-、||、~、^ 等符号。通过在版本号前面加上这些符号,可以有效的限制依赖的版本。
- version:必须依赖某个具体的版本。如:2.5.2,表示必须安装2.5.2版本。
- >version:必须大于某个版本。
- >=version:大于或等于某个版本。
- <version:必须小于某个版本。
- <=version:小于或等于某个版本
- x-range:x的位置可以为任意版本。
- *-range:任意版本。""也表示任意版本。
- version1 - version2:大于等于version1,小于等于version2。
- range1 || range2:满足range1或者满足range2,可以有多个范围。
- ~version:大概匹配某个版本。
- 如果次版本号(Y)指定了,那么次版本号(Y)不变,而修订号(Z)任意。
- 如果次版本号(Y)和修订号(Z)未指定,那么次版本号(Y)和修订号(Z)任意。
- ^version:向上兼容某个版本。
○ 从版本号最左侧开始,到首个非零数字,这些位置固定,其余位置任意。如果缺少某个位置,则这个位置可以任意。
json
"~2.5.2" // 表示 >=2.5.2 且 <2.6.0
"~2.5" // 表示 >=2.5.0 且 <2.6.0
"~2" // 表示 >=2.0.0 且 <3.0.0
json
"^2.5.2" // 表示 >=2.5.2 且 <3.0.0
"^0.1.3" // 表示 >=0.1.3 且 <0.2.0
"^0.0" // 表示 >=0.0.0 且 <0.1.0
使用npm安装依赖时在 package.json 里生成的默认是 ^version 形式,安装时会安装最新的依赖。
- 这意味着,在多人协作时,如果一个人安装了某个版本,另一个人在安装时这个依赖更新了此版本号,就会安装另一个版本,不同的版本会有差异,可能就会出现问题。
- 另外在本地构建和服务器构建时可能也会有类似的差异导致问题。
三、依赖锁定
如果所有的node包都严格符合语义化版本(semver)管理的规则,那么npm的最优版本号就能保证所下载的依赖包一定是与代码兼容的。
由于无法保证这一前提,如果想要保证他人下载的依赖包与我们的代码绝对兼容,就需要锁定项目中依赖包的版本号。
1. 写死版本号锁定
最简单的方法,就是指定具体版本号,可以将package.json中版本号开头的 ^ 和 ~ 等标记去掉。
后续安装新的依赖包时,则使用 npm i --save-exact <package_name> 或者 npm i --save <package_name>@1.2.3(指定依赖的具体版本),这样package.json中就不会出现最优版本的标记。
- 缺陷:无法锁定次级依赖的版本号,即依赖包的依赖包。
2. 锁文件
由于在重新安装依赖时,依赖树模块的版本存在着不确定性,为了解决这个问题,npm提供了 npm-shrinkwrap.json 或 package-lock.json 文件,这两种文件被称为包锁或锁文件。
1)npm shrinkwrap
在npm 5版本以前(不包括npm 5),可以通过执行 npm shrinkwrap 命令,创建一个新的或覆盖已有的 npm-shrinkwrap.json 文件。该文件记录了目前所有依赖包(及更底层依赖包)的版本信息。
当再次运行npm install命令重新安装依赖时,npm首先会找npm-shrinkwrap.json文件,依照其中的信息来准确地安装每一个依赖包,只有当这个文件不存在时,npm才会使用package.json。
- 每次更新package.json或者node_modules时,如: npm install新包、npm update、npm uninstall等操作,为了保证所有开发人员的资源一致,还是要手动运行npm shrinkwrap更新npm-shrinkwrap.json文件。
- npm shrinkwrap计算时是根据当前依赖安装的目录结构生成的,如果不能保证package.json文件定义的依赖与node_modules下已安装的依赖是匹配、无冗余的,建议在执行npm shrinkwrap命令前清理依赖并重新安装(rm-rf node_modules&&npm install)或精简依赖(npm prune)。
2)package-lock.json
在npm 5以后,运行npm intall会自动生成一个新文件package-lock.json,其内容跟上面提到的npm-shrinkwrap.json基本一样,在修改pacakge.json或者node_modules时会自动产生或更新它。
当项目中已存在package-lock.json文件,再安装项目依赖时,将以该文件为主进行解析安装指定版本的依赖包,而不是使用package.json来解析和安装。
因为package-lock.json为每个模块及其每个依赖项都指定了版本、位置和完整性哈希,所以它每次创建的安装都是相同的。
- cnpm并不支持package-lock。 使用cnpm install时,并不会生成package-lock.json文件。即使项目中已有package-lock.json文件,执行cnpm install命令,cnpm 也不会识别,仍会根据package.json安装依赖。因此,尽量避免直接使用cnpm install安装项目的依赖。
3)区别和联系
- package-lock.json是npm 5的新特性,且不向下兼容,因此如果npm版本是5以下,还是使用npm shrinkwrap命令。
- package-lock.json和npm-shrinkwrap.json这两个文件的优先级都比package.json高。
- 同一个项目里,如果不存在这两个文件,在运行npm install或者初始化项目npm init时,会自动生成一个package-lock.json(npm 5及以上)。
- 如果这两个文件都存在,安装依赖则是依据npm-shrinkwrap.json,而忽略package-lock.json。
- 如果项目里不存在package-lock.json,运行命令npm shrinkwrap后,会创建一个npm-shrinkwrap.json文件
- 如果存在package-lock.json,则会将其重命名为npm-shrinkwrap.json。
- npm-shrinkwrap.json只有在运行npm shrinkwrap命令时才会创建或更新;而package-lock.json会在修改pacakge.json或者node_modules时自动产生或更新。
四、要不要锁
一直以来这都是个有争议的问题。
1. 两种锁的方式:
- 一是在package.json里对个别依赖写死版本号锁定。
- 二是通过锁文件对所有依赖都锁定。
2. 个人建议:
- 当项目的维护可能陷入停滞或者很少更新时,通过锁文件来锁住依赖,保证项目即使过了很长时间依然能稳定跑起来。
- 当项目和依赖有专门的维护团队来长期维护时,不锁依赖,一般这种依赖的版本更新会比较规范,有问题也能及时发现和修复。
○ 如果是个别依赖的维护不稳定,这部分依赖可以通过package.json写死版本号来锁定。
其他情况就需要综合考量了,利弊都有,没有绝对的说法。