跳到主要内容

包管理工具

包管理器负责 解析依赖图、写下锁文件、把代码落到磁盘。前端工程化从这里开始:装错树,后面 Vite、CI、Docker 都会跟着错。构建见 构建工具与 Vite;多包仓库见 Monorepo 与多环境发布。

很多仓库用哪一种包管理器不是你说了算的——先认锁文件和 CI 里的安装命令。

解决什么问题​

痛点包管理器怎么帮
「我机器能装上、CI 不行」锁文件钉死版本树
依赖重复占磁盘pnpm 内容寻址,拷贝/硬链接复用
代码 import 了没声明的包提升(hoist)造成幽灵依赖;pnpm 默认隔离
安装慢、结果漂用锁文件 + 同一包管理器,禁止混用

一句话:声明在 package.json,复现靠锁文件,协作单元是「同一种安装器 + 同一把锁」。

先认现场​

标志含义你通常该怎么做
pnpm-lock.yamlpnpmpnpm install;CI 同样
yarn.lockYarn看是 Classic 还是 Berry(是否有 .yarn/)
package-lock.jsonnpmnpm ci(CI)/ npm install
多把锁同时存在有人混用过停下来对齐,不要再 install 一把新的
packageManager 字段Corepack 钉版本跟仓库指定的 pnpm/yarn 版本

本知识库仓库用 pnpm(见根目录 package.json 的 packageManager)。

三种工具(对照)​

早期 npm 嵌套 node_modules 造成「嵌套地狱」;npm 3 起改为平铺。Yarn Classic 也平铺。平铺的副作用是:你可能 require 到未声明的包(幽灵依赖)。

pnpm 默认用软链把直接依赖链到项目,依赖本体放在内容寻址存储里,既省盘,又减少幽灵依赖。

以下三项也常见于 ESLint 等工具的安装提示。

功能pnpmyarnnpm
工作空间(monorepo)✔️✔️✔️
隔离的 node_modules✔️ 默认✔️❌
提升的 node_modules可配✔️✔️ 默认
自动安装 peers可配 auto-install-peers❌✔️
Plug'n'Play✔️✔️ Yarn Berry 默认❌
零安装❌✔️ Berry❌
修补依赖✔️✔️❌
管理 Node 版本✔️❌❌
锁文件pnpm-lock.yamlyarn.lockpackage-lock.json
覆盖版本pnpm.overridesresolutionsoverrides
内容可寻址存储✔️❌❌
动态包执行pnpm dlxyarn dlxnpx

日常全栈:新项目优先 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 里记下「何时能删」。

要点​

  1. 先认锁文件,再决定用 npm / yarn / pnpm
  2. 可复现安装 = 锁文件 + frozen install
  3. 幽灵依赖来自 hoist;pnpm 隔离能提前暴露
  4. 不要在一个仓库里混用两种安装器
  5. overrides 是权宜之计,不是架构

面试速记​

  1. 锁文件干什么? 钉死完整依赖树,保证本机/CI/同事安装结果一致。
  2. 什么是幽灵依赖? 用了未在自己 package.json 声明、靠扁平化碰巧能解析到的包。
  3. pnpm 相对 npm 的两个卖点? 磁盘去重 + 默认更严的依赖隔离。
  4. CI 为什么用 npm ci / --frozen-lockfile? 禁止安装过程改锁,不一致直接失败。
  5. dependencies 和 devDependencies 怎么分? 运行/产物需要 vs 只在开发与构建需要;以部署方式为准。