package-lock.json解决依赖的版本管理使用详解
本文深入解析package-lock.json的作用与机制,阐述其与package.json的关系。详细说明npm install的安装策略、版本号语义规范(^、~、*)以及依赖树的生成逻辑。通过具体场景分析版本冲突原因,并介绍如何查看实际安装版本及处理文件冲突,帮助开发者实现项目依赖的精准控制。
前言
在上一篇关于package.json的文章中,我们探讨了其基本用法。通常与它相伴出现的还有一个package-lock.json文件,这两者之间究竟存在怎样的关联?本文将为您详细解答。
介绍
package-lock.json文件会在npm修改node_modules目录结构或package.json内容时自动生成。它精确记录了当前项目npm包的依赖树,并在后续安装过程中依据该文件进行依赖安装,从而确保依赖树的一致性,即使在此过程中某些依赖有小版本更新,也能保持锁定状态。
为什么需要package-lock.json
许多人可能和我一样存在疑问:既然已经有了package.json,为什么还需要package-lock.json?事实上,这两者并非在同一时期提出。package-lock.json是在npm5版本之后才引入的。从MDN的介绍来看,它的出现主要是为了解决依赖版本管理的问题。
执行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中的semver-range version规范,此时第二位开发者执行npm install后,A包的版本变为1.0.8。这种依赖版本的差异可能导致bug。 - 针对上述问题,有人可能认为将A的版本固定为
A: '1.0.5'即可解决问题。然而,这种做法并未根本解决矛盾。例如,A包的某个依赖在第一位开发者下载时是2.1.3版本,而第二位开发者下载时已升级至2.2.5。此时生成的node_modules目录树依然不完全相同。固定版本仅能锁定自身版本,无法固定其依赖的版本。
关于依赖的版本
我们可以先来了解依赖版本的构成。
{
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
}
}常见的依赖版本格式通常由三部分组成:major.minor.patch,依次代表主版本号、次版本号和修补版本号。
主要版本的更改代表了一个破坏兼容性的大版本更新。如果用户无法适应主要版本的更改,内容将无法正常工作。次要版本的更改表示增加了不会破坏现有功能的新特性。修补版本的更改表示进行了不会破坏现有功能的错误修复。
例如上面我们看到的^10.0.0,其中主版本号为10、次版本号为0、修补版本号为0。那么^符号表示什么呢?
版本号指定标识符
这个符号用于指定版本范围,与之对应的有以下符号:
^会匹配最新的大版本依赖包。例如,^1.2.3会匹配所有>=1.1.2 <2.0.0版本的包,包括1.3.0,但不包括2.0.0。~会匹配最近的小版本依赖包。例如,~1.2.3会匹配所有>=1.1.2 <1.2.0版本的包,但不包括1.3.0。*表示安装最新版本的依赖包。例如,*1.2.3会匹配x.x.x格式的任何版本。- 当无符号时,例如
1.2.3,则表示将要使用的确切版本,总是会下载该特定版本的依赖包。
认识package-lock.json
这个文件看起来比package.json文件更大且更复杂,动辄包含上万行代码。
我们可以只安装某一个依赖来看看它内部的结构,例如axios:
{
"name": "demo",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"asynckit": {
"version": "0.4.0",
"resolved": "https://mirrors.tencent.com/npm/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"axios": {
"version": "1.4.0",
"resolved": "https://mirrors.tencent.com/npm/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://mirrors.tencent.com/npm/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://mirrors.tencent.com/npm/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://mirrors.tencent.com/npm/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"form-data": {
"version": "4.0.0",
"resolved": "https://mirrors.tencent.com/npm/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://mirrors.tencent.com/npm/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://mirrors.tencent.com/npm/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://mirrors.tencent.com/npm/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
}
}
}从这里我们可以发现,它的dependencies字段与package.json中的定义并不完全一样。除了axios本身之外,它还包含了一些其他的依赖。实际上,这些其他的依赖都是axios的依赖,或者是它依赖的依赖……
从上面的介绍中我们也能知道,它实际上描述的是当前项目的完整依赖树。
它有一些与package.json文件中不同的属性,例如:
lockfileVersion
一个整数版本,从1开始,表示该文档的版本号。
resolved
依赖的安装地址,实际上就是一个包的下载地址。
intergrity
表示解压的完整性Hash值。
dev
表示该模块是否为顶级模块的开发依赖,或者是传递依赖关系。
requires
依赖包所需要的所有依赖项,对应依赖包package.json里dependencices中的依赖项。
npm install策略
当我们每次使用npm install进行依赖安装的时候,它到底是按照什么规则去帮我们下载依赖的呢?
这里其实有好几个版本,但我们只需要了解最新版本就行了。
先看有无lock文件:
如果有,则对比package.json和package-lock.json。
- 如果
package-lock.json里包版本号符合package.json要求,则直接获取包信息(如果是从远程拉取,则按照package-lock.json,否则以实际缓存的为准),构建依赖树(注意这一步只是确定逻辑上的依赖树,并非真正的安装,后面会根据这个依赖结构去下载或拿到缓存中的依赖包);那么接下来就看.npmrc里有没有缓存,如果有缓存文件,则从缓存文件中拉取内容,否则从远程拉取;并更改package-lock。json版本号。 - 如果版本号不符合要求,则直接从远程拉取,并更新
package-lock.json中的版本号。
如果没有,则:
- 根据
package.json构建依赖树(注意这一步只是确定逻辑上的依赖树,并非真正的安装,后面会根据这个依赖结构去下载或拿到缓存中的依赖包)。 - 如果缓存中
(.npmrc)有,则优先从缓存中读取,否则从远程读取;注意:(如果是从远程拉取,则按照package.json,否则以实际缓存的为准)。
需要注意的是,在使用cnpm install的时候,并不会生成package-lock.json文件,也不会根据package-lock.json来安装依赖,它只会根据package.json来安装依赖。
场景一
// package.json
"dependencies": {
"vue": "^2.0.0"
}
// package-lock.json
"dependencies": {
"vue": {
"version": "2.7.14",
"resolved": "https://mirrors.tencent.com/npm/vue/-/vue-2.7.14.tgz",
"integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
"requires": {
"@vue/compiler-sfc": "2.7.14",
"csstype": "^3.1.0"
}
}
}
这种情况下package-lock.json指定的2.7.14在^2.0.0指定的范围内,npm install会安装vue2.7.14版本。
场景二
// package.json
"dependencies": {
"vue": "^2.2.0"
}
// package-lock.json
"dependencies": {
"vue": {
"version": "2.1.0",
"resolved": "https://mirrors.tencent.com/npm/vue/-/vue-2.1.0.tgz",
"integrity": "sha1-KTuj76rKhGqmvL+sRc+FJMxZfj0="
}
}
这种情况下package-lock.json指定的2.1.0不在^2.2.0指定的范围内,npm install会按照^2.2.0的规则去安装最新的2.7.14版本,并且将package-lock.json的版本更新为2.7.14。
现在应该能够理解package.json文件是如何做到对依赖进行版本锁定的吧。我们一般在安装依赖时如果不指定版本,那么安装的版本号并不是固定的而是一个最优版本,最优版本会在版本前多了一个^或者~符号。
"dependencies": {
"vue": "^2.0.0"
}但我们的lock文件中肯定是会指定一个固定版本进行安装的,一般是改依赖的符合版本范围的最新版本。
"dependencies": {
"vue": {
"version": "2.1.0",
"resolved": "https://mirrors.tencent.com/npm/vue/-/vue-2.1.0.tgz",
"integrity": "sha1-KTuj76rKhGqmvL+sRc+FJMxZfj0="
}
}至于为什么不直接在package.json中将版本锁定,那是因为你只能指定你安装的依赖的版本,但不能指定你依赖的依赖的版本。
package-lock.json什么时候会变?
开发过程中是不是经常遇到这个文件冲突的情况?自己明明没改这个文件为啥会冲突?那是因为我们的一些操作会影响到该文件的内容,例如:
package-lock.json在npm install的时候会自动生成- 当我们修改依赖位置,例如将部分依赖从
开发依赖改改为生产依赖时,虽然整体上的依赖并未改变,但是也会影响package-lock.json中依赖的dev字段。 - 如果我们切换
npm镜像时,执行npm install命令时也会修改package-lock.json,因为它是会记录我们的依赖包地址的(resolved)。 - 当我们使用
npm install添加或npm uninstall移除包的时候,也会修改package-lock.json。 - 当我们更新某个包的版本的时候,也会修改
package-lock.json。
package-lock.json需要提交到仓库吗?
npm官网建议:把package-lock.json一起提交到代码库中,不要ignore。但是在执行npm publish的时候,它会被忽略而不会发布出去。
如何查看依赖安装的版本?
上面我们已经了解到,package.json中保存的依赖版本一般不是一个具体版本,而是一个带有^或~的最优版本,那我们怎么才能知道当前项目依赖安装的具体版本呢?
- 查看
package-lock.json文件,这里保存的是依赖的具体版本。 - 从
node_modules文件夹中找到对应依赖的package.json文件,里面的version字段就是该依赖的版本。 - 使用
npm list --depth 0命令查看项目所有的依赖版本。

以上就是package-lock.json解决依赖的版本管理使用详解的详细内容,更多关于package-lock.json版本依赖的资料请关注本站其它相关文章!
生产环境怎么自动移除JS的console.log语句?
2026-11-03 10:59:54
面试刷题必备:怎么利用在线JS沙箱高效验证算法
2026-11-03 09:11:28
使用 Vite 构建 Phaser.js 项目的配置与资源管理
2026-11-02 10:26:07
webpack 自动清理 dist 文件夹的两种实现方法
2026-10-31 14:14:29
Docker部署Jackett完整指南:CLI与Compose配置及索引器使用
2026-10-31 12:57:10
