包管理工具
包管理器负责 解析依赖图、写下锁文件、把代码落到磁盘。前端工程化从这里开始:装错树,后面 Vite、CI、Docker 都会跟着错。构建见 构建工具与 Vite;多包仓库见 Monorepo 与多环境发布。
很多仓库用哪一种包管理器不是你说了算的——先认锁文件和 CI 里的安装命令。
解决什么问题
| 痛点 | 包管理器怎么帮 |
|---|---|
| 「我机器能装上、CI 不行」 | 锁文件钉死版本树 |
| 依赖重复占磁盘 | pnpm 内容寻址,拷贝/硬链接复用 |
代码 import 了没声明的包 | 提升(hoist)造成幽灵依赖;pnpm 默认隔离 |
| 安装慢、结果漂 | 用锁文件 + 同一包管理器,禁止混用 |
一句话:声明在 package.json,复现靠锁文件,协作单元是「同一种安装器 + 同一把锁」。
先认现场
| 标志 | 含义 | 你通常该怎么做 |
|---|---|---|
pnpm-lock.yaml | pnpm | pnpm install;CI 同样 |
yarn.lock | Yarn | 看是 Classic 还是 Berry(是否有 .yarn/) |
package-lock.json | npm | npm ci(CI)/ npm install |
| 多把锁同时存在 | 有人混用过 | 停下来对齐,不要再 install 一把新的 |
packageManager 字段 | Corepack 钉版本 | 跟仓库指定的 pnpm/yarn 版本 |
本知识库仓库用 pnpm(见根目录 package.json 的 packageManager)。
三种工具(对照)
早期 npm 嵌套 node_modules 造成「嵌套地狱」;npm 3 起改为平铺。Yarn Classic 也平铺。平铺的副作用是:你可能 require 到未声明的包(幽灵依赖)。
pnpm 默认用软链把直接依赖链到项目,依赖本体放在内容寻址存储里,既省盘,又减少幽灵依赖。
以下三项也常见于 ESLint 等工具的安装提示。
| 功能 | pnpm | yarn | npm |
|---|---|---|---|
| 工作空间(monorepo) | ✔️ | ✔️ | ✔️ |
隔离的 node_modules | ✔️ 默认 | ✔️ | ❌ |
提升的 node_modules | 可配 | ✔️ | ✔️ 默认 |
| 自动安装 peers | 可配 auto-install-peers | ❌ | ✔️ |
| Plug'n'Play | ✔️ | ✔️ Yarn Berry 默认 | ❌ |
| 零安装 | ❌ | ✔️ Berry | ❌ |
| 修补依赖 | ✔️ | ✔️ | ❌ |
| 管理 Node 版本 | ✔️ | ❌ | ❌ |
| 锁文件 | pnpm-lock.yaml | yarn.lock | package-lock.json |
| 覆盖版本 | pnpm.overrides | resolutions | overrides |
| 内容可寻址存储 | ✔️ | ❌ | ❌ |
| 动态包执行 | pnpm dlx | yarn dlx | npx |
日常全栈:新项目优先 pnpm;进已有仓库则跟锁文件走。
锁文件与 CI
# 本地改依赖后会更新锁文件,需要提交
pnpm add lodash
pnpm add -D typescript
# CI:按锁文件精确安装,不要在流水线里无锁升级
pnpm install --frozen-lockfile
# npm 对应:npm ci
| 原则 | 原因 |
|---|---|
| 锁文件进 Git | 否则每人/每次 CI 解析出不同树 |
CI 用 frozen / npm ci | 发现锁与 package.json 不一致就失败,而不是悄悄改锁 |
| 同一仓库一种安装器 | 两把锁会来回覆盖,diff 灾难 |
dependencies vs devDependencies:生产镜像应只装运行时依赖(pnpm install --prod 或 Docker 多阶段)。前端 打包进 bundle 的库 仍常放 dependencies;只在构建机用的 Vite/ESLint 放 devDependencies。以「产物里是否还需要 node_modules 里这份包」为准,不要死记。
幽灵依赖
平铺后,A 依赖 B,B 依赖 C,你的代码可能 import "c" 却没在自己的 package.json 声明。本地碰巧能跑,B 升级不再依赖 C 时生产炸掉。
pnpm 默认不让你 import 未声明的包——这是特性。补上声明,或从真正导出它的包走。
覆盖与补丁
上游有 bug、暂时不能升大版本时:
- npm / pnpm:
overrides强制整棵树用某版本 - Yarn Classic:
resolutions - 改源码:pnpm
pnpm patch生成补丁文件并入库
覆盖是债务,在 issue 里记下「何时能删」。
要点
- 先认锁文件,再决定用 npm / yarn / pnpm
- 可复现安装 = 锁文件 + frozen install
- 幽灵依赖来自 hoist;pnpm 隔离能提前暴露
- 不要在一个仓库里混用两种安装器
overrides是权宜之计,不是架构
面试速记
- 锁文件干什么? 钉死完整依赖树,保证本机/CI/同事安装结果一致。
- 什么是幽灵依赖? 用了未在自己
package.json声明、靠扁平化碰巧能解析到的包。 - pnpm 相对 npm 的两个卖点? 磁盘去重 + 默认更严的依赖隔离。
- CI 为什么用
npm ci/--frozen-lockfile? 禁止安装过程改锁,不一致直接失败。 dependencies和devDependencies怎么分? 运行/产物需要 vs 只在开发与构建需要;以部署方式为准。