六维教程

NPM 依赖解析

执行完 npm install 后,项目里多了两个东西:node_modules 文件夹和 package-lock.json 文件。

它们是什么?有什么用?这篇来拆解清楚。

node_modules 文件夹

node_modules 是所有依赖包实际存放的地方。你执行 npm i,所有代码就下载到这里。

my-app/
├── node_modules/        ← 所有依赖都在这里
│   ├── axios/
│   ├── lodash/
│   ├── react/
│   └── ...
├── package.json
└── package-lock.json

为什么 node_modules 特别大

一个项目往往有几十上百个依赖,每个依赖又有自己的依赖,层层叠加。比如你装了 10 个包,实际下载的可能有 100 个。

你的项目
├── axios
│   └── follow-redirects  ← axios 的依赖
├── react
└── lodash

axios 本身又依赖了 follow-redirects,NPM 会一起装进来。这就是“间接依赖”或“子依赖”。

node_modules 的结构

早期 NPM(v2)是嵌套结构

node_modules/
├── package-a/
│   └── node_modules/
│       └── lodash/       ← package-a 自己的 lodash
└── package-b/
    └── node_modules/
        └── lodash/       ← package-b 自己的 lodash

问题很明显:lodash 被装了两次,重复占用空间。

现在 NPM(v3+)使用扁平化结构

node_modules/
├── lodash/               ← 所有包共用同一个 lodash
├── package-a/
├── package-b/

能扁平就扁平,实在不行(版本冲突)才保留嵌套。

版本冲突时怎么办

项目依赖了两个包,一个要 lodash@4,一个要 lodash@5

node_modules/
├── lodash/5.x 版本(提升到顶层)
├── package-a/
│   └── node_modules/
│       └── lodash/4.x 版本(自己的私有版本)
└── package-b/            ← 用顶层的 5.x

NPM 会把最常用或最先安装的版本放在顶层,冲突的版本放在需要它的包的子目录里。

作为新手,你不需要记住这些细节,只需要知道两点:

  1. node_modules 是依赖的实际存放位置,正常情况下不要手动修改
  2. 如果 node_modules 出了问题,直接删掉重新 npm i 能解决大部分问题

package-lock.json 文件

node_modules 是依赖的“实体”,package-lock.json 是依赖的“清单”。

package.json 不够吗

package.json 里记录的是版本范围(比如 ^4.17.21),而不是精确版本。不同时间执行 npm i,实际装到的版本可能不一样。

package.json 实际安装
记录 "lodash": "^4.17.21" 可能是 4.17.21、4.18.0、4.19.2……
确定性 ❌ 模糊 ✅ 精确

问题来了:你本地跑得好好的,同事拉代码后 npm i 装到了新版本,程序崩了——但代码明明一样。

package-lock.json 就是为了解决这个问题。

lock 文件做了什么

完整记录了当时安装的精确版本和依赖树结构

{
  "name": "my-app",
  "lockfileVersion": 3,
  "packages": {
    "node_modules/axios": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
      "dependencies": {
        "follow-redirects": "^1.15.0"
      }
    },
    "node_modules/follow-redirects": {
      "version": "1.15.3",
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz"
    }
  }
}

关键信息:

  • 每个包的精确版本号1.6.0 而不是 ^1.6.0
  • 下载地址(resolved
  • 依赖树结构

锁文件的核心价值

场景 有 lock 文件 没有 lock 文件
团队成员安装 装的版本完全一致 可能装到不同版本
CI/CD 构建 稳定可重现 每次可能不同
问题排查 知道用的什么版本 版本说不清

一句话总结:package-lock.json 锁住的是“确定性”,让所有人、所有环境装出来的依赖一模一样。

日常使用规则

场景 要不要提交 lock 文件 做法
应用项目(网站/App/服务) 必须提交 提交到 Git,保证团队一致
库/包(你要发布到 NPM 的) ❌ 不提交 发布时会自动忽略,让使用者自行解析
新增/删除依赖 ✅ 提交更新后的 lock npm i 会自动更新 lock 文件

package.json vs package-lock.json

对比维度 package.json package-lock.json
作用 声明需要什么包 记录实际装的精确版本
版本记录 范围(如 ^1.6.0 精确(如 1.6.0
需要手动改吗 部分场景需要 不需要,NPM 自动维护
提交到 Git ✅ 必须 ✅ 必须(应用项目)
依赖关系 只记录直接依赖 记录完整的依赖树

几个实用命令

查看已安装的依赖

# 列出所有依赖(顶层)
npm list --depth=0

# 查看某个包的版本
npm list lodash

查看 package-lock.json 中某个包的版本

# 不需要专门命令,直接搜索 lock 文件
cat package-lock.json | grep "lodash"

手动重建 node_modules

node_modules 疑似出问题时:

# 删掉重装,简单粗暴有效
rm -rf node_modules package-lock.json
npm i

新手常见问题

Q:package-lock.json 需要手动编辑吗?

完全不需要。 它由 NPM 自动生成和更新,手动改只会自找麻烦。

Q:Git 冲突时 package-lock.json 怎么办?

两个人都在 main 分支安装了不同的包,合并时 lock 文件可能冲突。解决方案:

  1. 直接用 npm install 重新生成(删掉 lock 再 npm i,让 NPM 重新解析)
  2. 或用 npm install --package-lock-only 更新 lock 文件

Q:可以删除 package-lock.json 吗?

可以删,但执行 npm i 后会重新生成。如果团队成员版本不一致,删掉后重新生成反而能统一。

Q:node_modules 能删吗?

可以随便删。 删掉后执行 npm i 就能重新下载。如果你的项目仓库体积太大,通常做法就是删除 node_modules 再重新安装。

Q:一个项目有多个 package-lock.json 吗?

正常情况下只有根目录一个。如果项目里有多个子包(monorepo),可能会有多个。

Q:package-lock.json 存在时,npm i 就一定按它装吗?

默认是的。但如果 package.jsonpackage-lock.json 记录的范围不一致,npm i 会以 package.json 为准,更新锁文件。如果你希望绝对按锁文件安装,可以改用 npm ci 命令。

本篇小结

node_modulespackage-lock.json 是两个容易混淆的概念,记住这句就够了:

package.json 说“我想要什么”,package-lock.json 说“我装了什么”,node_modules 放“装好的东西”。

上一篇
NPM install命令
下一篇
NPM scripts脚本