4. 依赖管理
依赖管理,说白了就是管好你项目里那些“借来的代码”。
我刚开始做项目时,觉得这事很简单——缺什么装什么呗。直到有一次,一个同事更新了某个底层库,结果整个项目炸了。嗯,从那以后我才真正重视起依赖管理。
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 看看依赖树。这样能提前发现版本冲突或冗余依赖。
为什么会关心这个?因为传递依赖可能带来两个问题:
- 版本冲突:A 包要 1.x,B 包要 2.x,结果两个版本都装进来了
- 安全漏洞:某个深层依赖有漏洞,你根本不知道
我曾经排查过一个线上 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 了。