npm依赖版本锁定详解

npm中有一个package-lock.json的文件,即npm依赖锁文件,用来描述npm依赖生成的确切树,这样不管你的依赖有何种更新,都会按照这个确切树来安装使用。

  • 不同的包管理工具对应不同的锁文件:
    • npm => package-lock.json
    • yarn => yarn.lock
    • pnpm => pnpm-lock.yaml

整体上大同小异,本文主要以npm为例。

一、semver 版本控制规范

在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。
为了解决依赖包的版本混乱问题,制定了这个 semver 规范。

1. 版本格式

  • 主版本号.次版本号.修订号
  • major.minor.patch
  • X.Y.Z

2. 版本号递增规则

  • 主版本号 major:当你做了不兼容的 API 修改,(不兼容更新
  • 次版本号 minor:当你做了向下兼容的功能性新增,(功能新增
  • 修订号 patch:当你做了向下兼容的问题修正。(bug修复

(更详细内容参考官网文档:https://semver.org/lang/zh-CN/)

二、安装依赖时的版本规则

npm依赖规则中,还有 >、>=、<、<=、x、*、-、||、~、^ 等符号。通过在版本号前面加上这些符号,可以有效的限制依赖的版本。

1. 版本规则

  • version:必须依赖某个具体的版本。如:2.5.2,表示必须安装2.5.2版本。
  • ^version:向上兼容某个版本。
    • 从版本号最左侧开始,到首个非零数字,这些位置固定,其余位置任意。如果缺少某个位置,则这个位置可以任意。
    • npm i 安装新包时的默认方式
      • "^2.5.2" // 表示 >=2.5.2 且 <3.0.0
      • "^0.1.3" // 表示 >=0.1.3 且 <0.2.0,主版本为0的比较特殊
      • "^0.0" // 表示 >=0.0.0 且 <0.1.0
  • ~version:大概匹配某个版本。
    • 如果次版本号(Y)指定了,那么次版本号(Y)不变,而修订号(Z)任意。
    • 如果次版本号(Y)和修订号(Z)未指定,那么次版本号(Y)和修订号(Z)任意。
      • "~2.5.2" // 表示 >=2.5.2 且 <2.6.0
      • "~2.5" // 表示 >=2.5.0 且 <2.6.0
      • "~2" // 表示 >=2.0.0 且 <3.0.0
  • >version:必须大于某个版本。
  • >=version:大于或等于某个版本。
  • <version:必须小于某个版本。
  • <=version:小于或等于某个版本。
  • version1 - version2:大于等于version1,小于等于version2。

(一般项目里前三种比较常用,后面几种在npm包里可能会用到)

2. 存在的问题

使用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. oversides

oversides 字段允许开发者指定依赖的具体版本,当安装依赖时,npm会以指定的版本为准进行安装。这主要用于解决嵌套依赖中的版本冲突问题,确保项目中的所有依赖都使用一致的版本‌。

"oversides": {
  "package-c": "^1.2.0"
}
  • 常见场景
    • 子依赖版本冲突:多个包依赖于不同版本的同一子包时,可通过 overrides 统一锁定版本。
    • 控制版本兼容性:强制指定某些子依赖的版本,以避免运行时错误。
    • 修复安全漏洞:上游库未及时更新依赖库,而你需要修复子依赖中的漏洞。
  • yarn 中有类似的解法,字段名为 resolutions
  • 需要 npm v8.3.0(node 16)及以上版本支持。

3. 锁文件 package-lock.json

由于在重新安装依赖时,依赖树模块的版本存在着不确定性,为了解决这个问题,npm提供了 package-lock.json 文件,被称为锁文件。

当项目中已存在package-lock.json文件,再安装项目依赖时,将以该文件为主进行解析安装指定版本的依赖包,而不是使用package.json来解析和安装。

因为package-lock.json为每个模块及其每个依赖项都指定了版本、位置和完整性哈希,所以它每次创建的安装都是相同的。

3.1 锁文件的作用
  • 锁定版本
    • package-lock.json 文件锁定了所有直接和间接依赖的版本号,不论是 major、minor 还是 patch 版本。这意味着即使 package.json 中使用了版本范围符号(如 ^、~),package-lock.json 仍然会记录下当前安装的确切版本。
  • 加速安装
    • 通过记录依赖关系和版本信息,npm 可以在后续安装中跳过解析步骤,从而加快安装速度。
  • 一致性
    • 确保在不同机器上执行 npm install 时安装的依赖完全一致,不会因为发布了新的不兼容版本而导致项目无法正常工作。
3.2 锁文件的结构
  • 常见字段:
    • dependencies:包含所有直接和间接依赖的详细信息,包括版本号、来源等。
    • version:每个包的版本号。
    • resolved:包的下载地址。
    • integrity:用于验证包完整性的哈希值。
    • dev:标识该依赖是否为开发环境依赖。
3.3 锁文件的版本

package-lock.json 文件的格式和内容会随着 npm 版本的变化而有所不同。
lockfileVersion 字段定义了锁文件的版本。

3.4 锁文件的最佳实践
  • 始终提交锁文件到版本控制系统
    • 提交 package-lock.json 可以确保开发环境和生产环境安装的依赖版本一致。
  • 避免手动编辑锁文件
    • 锁文件是自动生成的,手动编辑可能导致安装时出错。应通过修改 package.json 然后运行 npm install 来更新锁文件。
  • 使用相同的 npm(node)版本
    • 不同的 npm(node)版本可能生成的锁文件会有细微差异,特别是锁文件格式(如 lockfileVersion)。
    • 团队成员可以统一使用某一版本的 npm(node),例如通过 .nvmrc 文件指定 node.js 版本,从而确保一致性。
  • 生产环境使用 npm ci
    • 在生产环境中部署项目时,建议使用 npm ci 替代 npm install。
    • npm ci 会完全按照 package-lock.json 中的版本安装依赖,而忽略 package.json 中的版本范围。

四、要不要锁

一直以来这都是个有争议的问题。

1. 建议

  • 当项目的维护可能陷入停滞或者很少更新时,通过锁文件来锁住依赖,保证项目即使过了很长时间依然能稳定跑起来。
  • 当项目和依赖有专门的维护团队来长期维护时,不锁依赖,一般这种依赖的版本更新会比较规范,有问题也能及时发现和修复。
    • 如果是个别依赖的维护不稳定,这部分依赖可以通过package.json写死版本号或oversides来锁定。

最终还是需要综合考量,利弊都有,没有绝对的说法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值