几乎所有前端项目中都有 yarn.lock
/ package-lock.json
文件(以下以 npm 为例)。打开浏览,你会发现它长得类似 package.json
的依赖,但是冗长得多。很多同学可能都不知道他们是干什么用的,甚至部分同学在项目跑不起来的时候还会暴力把 lock 文件给干掉,最后反而导致更严重的错误。
那么,lock 文件到底是干什么用的?为什么我们会需要 lock 文件?
# 为什么需要 lock 文件
{
"name": "zqd-demo",
"version": "1.0.0",
"dependencies": {
"vue": "^2.5.0"
},
"devDependencies": {
"core-js": "^3.0.0",
"husky": "^4.0.0"
},
}
这是一个简单的 package.json
文件,dependencies
, devDependencies
记录了这个项目的依赖。我们看到依赖包的版本号前都有 ^
号,这种写法是因为 npm 使用了语义化版本规范 (opens new window)。这套规范定义了一组简单的规则及条件来约束版本号的配置和增长。semver 约定一个包的版本号必须包含 3 个数字,格式必须为 MAJOR.MINOR.PATCH
, 意为 主版本号.小版本号.修订版本号
:
MAJOR
对应大的版本号迭代,做了不兼容旧版的修改时要更新 MAJOR 版本号MINOR
对应小版本迭代,发生兼容旧版 API 的修改或功能更新时,更新 MINOR 版本号PATCH
对应修订版本号,一般针对修复 BUG 的版本号
对于包作者(发布者),npm 要求在 publish 之前,必须更新版本号。npm 提供了 npm version
工具,执行 npm version major|minor|patch
可以简单地将版本号中相应的数字加 1。可以从这里 (opens new window)了解到 npm 是如何使用它的。
如果包是一个 git 仓库,
npm version
还会自动创建一条注释为更新后版本号的 git commit 和名为该版本号的 tag
常用的规则示例如下表:
range | 含义 | 示例 |
---|---|---|
^2.2.1 | 指定的 MAJOR 版本号下, 所有更新的版本 | 匹配 2.2.3 , 2.3.0 ; 不匹配 1.0.3 , 3.0.1 |
~2.2.1 | 指定 MAJOR.MINOR 版本号下,所有更新的版本 | 匹配 2.2.3 , 2.2.9 ; 不匹配 2.3.0 , 2.4.5 |
>=2.1 | 版本号大于或等于 2.1.0 | 匹配 2.1.2 , 3.1 |
<=2.2 | 版本号小于或等于 2.2 | 匹配 1.0.0 , 2.2.1 , 2.2.11 |
1.0.0 - 2.0.0 | 版本号从 1.0.0 (含) 到 2.0.0 (含) | 匹配 1.0.0 , 1.3.4 , 2.0.0 |
npm install
执行后,会生成一个 node_modules
树,在理想情况下, 希望对于同一个 package.json
总是生成完全相同 node_modules
树。在某些情况下,确实如此。但在多数情况下,npm 无法做到这一点。有以下两个原因:
某些依赖项自上次安装以来,可能已发布了新版本。比如:A 包在团队中第一个人安装的时候是
1.0.5
版本,package.json
中的配置项为A: '^1.0.5'
;团队中第二个人把代码拉下来的时候,A 包的版本已经升级成了1.0.8
,根据package.json
中遵循的版本规范,此时第二个人npm install
后 A 的版本为1.0.8
; 可能会造成因为依赖版本不同而导致的 bug;针对 1 中的问题,可能有的小伙伴会想,把 A 的版本号固定为
A: '1.0.5'
不就可以了吗?但是这样的做法其实并没有解决问题,比如 A 的某个依赖在第一个人下载的时候是2.1.3
版本,但是第二个人下载的时候已经升级到了2.2.5
版本,此时生成的node_modules
树依旧不完全相同 ,固定版本只是固定来自身的版本,依赖的版本无法固定。
为了解决上述问题,在 npm 5.0+ 版本,npm install
后都会自动生成一个 package-lock.json
文件。当代码库中有 package-lock.json
文件,执行 npm install
,会判断 package.json
和 package-lock.json
中的版本是否兼容,如果兼容会根据 package-lock.json
中的版本下载;如果不兼容,将会根据 package.json
的版本,更新 package-lock.json
中的版本,以保证 package-lock.json
中的版本兼容 package.json
。
综上,我们可以得出,lock 文件的存在主要有以下意义:
- 记录所有的依赖项及相互依赖关系;
- 优化 npm / yarn 的安装过程:在安装时,npm 会把
node_modules
已有的包和package-lock.json
进行比较,如果重复的话,就跳过安装; - 保证依赖包一致性:在团队错人协作中,确保每个开发同学安装的依赖版本是一致的,确定一棵唯一的
node_modules
树; - 利于依赖包的管理维护:
node_modules
目录本身是不会被提交到代码库的,但是package-lock.json
可以而且必须提交到代码库,如果开发人员想要回溯到某一天的目录状态,只需要把package.json
和package-lock.json
这两个文件回退到那一天即可。
# lock 文件的结构
// package-lock.json
{
"name": "zqd-demo",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.8.3",
"resolved": "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.8.3.tgz?cache=0&sync_timestamp=1578953126105&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.8.3.tgz",
"integrity": "sha1-M+JZA9dIEYFTThLsCiXxa2/PQZ4=",
"dev": true,
"requires": {
"@babel/highlight": "^7.8.3"
}
},
// ...
}
}
这是一个精简过的 package-lock.json
文件,其中 name
, version
与 package.json
中的 name
, version
一样,描述了当前包的名字和版本,dependencies
是一个对象,该对象和 node_modules
中的包结构一一对应,对象的 key
为包的名称,值为包的一些描述信息, 根据 package-lock-json 官方文档 (opens new window),主要的结构如下:
version
:包版本,即这个包当前安装在node_modules
中的版本resolved
:包具体的安装来源integrity
:包 hash 值,验证已安装的软件包是否被改动过、是否已失效requires
:对应子依赖的依赖,与子依赖的package.json
中dependencies
的依赖项相同dependencies
:结构和外层的dependencies
结构相同,存储安装在子依赖node_modules
中的依赖包
需要注意的是,并不是所有的子依赖都有 dependencies
属性,只有子依赖的依赖和当前已安装在根目录的 node_modules
中的依赖冲突之后,才会有这个属性。