4. 依赖管理:显式依赖与隐式依赖,依赖树与传递依赖,锁定文件(lockfile)的作用

依赖管理,说白了就是管好你项目里那些“借来的代码”。

我刚开始做项目时,觉得这事很简单——缺什么装什么呗。直到有一次,一个同事更新了某个底层库,结果整个项目炸了。嗯,从那以后我才真正重视起依赖管理。

4.1 显式依赖与隐式依赖

先说说这两个概念。你写代码时,直接在 package.json 或 requirements.txt 里列出来的,就是显式依赖。比如你装了个 lodash,这就是显式的。

但 lodash 自己可能也依赖别的包,比如 lodash.merge。这些你没直接装、但确实存在的,就是隐式依赖。

关键区别:

  • 显式依赖:你主动声明,项目直接使用
  • 隐式依赖:被间接引入,你未必知道它存在

我在项目中遇到过最坑的一次:某个隐式依赖悄悄升级了,结果导致我们的打包体积暴增 30%。你想想看,排查这种问题有多痛苦。

4.2 依赖树与传递依赖

依赖之间不是平级的,它们会形成一棵树。这棵树就叫依赖树。

举个例子:

my-project
├── express@4.18.2
│   ├── accepts@1.3.8
│   │   ├── mime-types@2.1.35
│   │   └── negotiator@0.6.3
│   ├── body-parser@1.20.1
│   │   └── raw-body@2.5.1
│   └── qs@6.11.0
└── lodash@4.17.21

你只装了 express 和 lodash,但 express 自己又拉了 accepts、body-parser 这些。这就是传递依赖。

我的习惯:每次装包前,先用 npm ls 或 pipdeptree 看看依赖树。这样能提前发现版本冲突或冗余依赖。

为什么会关心这个?因为传递依赖可能带来两个问题:

  1. 版本冲突:A 包要 1.x,B 包要 2.x,结果两个版本都装进来了
  2. 安全漏洞:某个深层依赖有漏洞,你根本不知道

我曾经排查过一个线上 bug,折腾了两天。最后发现是某个三层深度的依赖版本不对。从那以后,我养成了定期检查依赖树的习惯。

4.3 锁定文件(lockfile)的作用

锁定文件,就是 package-lock.json、yarn.lock 或 Pipfile.lock 这类文件。

它的作用很简单:把每个依赖的精确版本固定下来。

你想想看,如果没有锁定文件,今天装的是 1.0.0,明天可能就是 1.0.1。如果 1.0.1 改了某个接口,你的代码就炸了。

注意:锁定文件一定要提交到版本控制里!我见过有人把 .gitignore 里加了 package-lock.json,结果团队里每个人装的版本都不一样。

锁定文件具体做了三件事:

作用 说明
版本固定 记录每个依赖的精确版本号,包括传递依赖
安装加速 跳过版本解析,直接下载指定版本
可复现 保证不同环境、不同时间安装的结果完全一致

举个例子,看看 package-lock.json 长什么样:

{
  "name": "my-project",
  "lockfileVersion": 2,
  "packages": {
    "": {
      "dependencies": {
        "express": "^4.18.2"
      }
    },
    "node_modules/express": {
      "version": "4.18.2",
      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
      "integrity": "sha512-...",
      "dependencies": {
        "accepts": "~1.3.8",
        "body-parser": "1.20.1"
      }
    },
    "node_modules/accepts": {
      "version": "1.3.8",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
      "integrity": "sha512-..."
    }
  }
}

看到没?每个包的版本、下载地址、校验值都写死了。这样不管谁装、什么时候装,结果都一样。

避坑指南:我曾经在 CI 环境里遇到过锁定文件没提交,导致每次构建都装到不同版本。后来加了 npm ci 命令,它会严格按锁定文件安装,版本不对就报错。

4.4 依赖管理的实践建议

说了这么多,总结几条我个人的经验:

  • 锁定文件必须提交:这是底线,别问为什么
  • 定期更新依赖:用 npm outdated 或 pip list --outdated 检查,但别盲目升级
  • 关注传递依赖:用 npm audit 或 safety check 扫描安全漏洞
  • 减少隐式依赖:能用标准库就别乱装包,依赖越少越好

说白了,依赖管理就是管好你借来的东西。借的时候记个账(显式依赖),知道借了谁的东西(依赖树),还要保证每次借到的都一样(锁定文件)。

嗯,做到这几点,你的项目就不会因为依赖问题半夜被叫起来修 bug 了。