为什么写这篇:2016 年 yarn 1.0 横空出世,靠并行安装和 lockfile 把 npm 5 之前的"装包慢、依赖飘"问题锤了一遍。2018 年 pnpm 出现,靠"硬链接 + 内容寻址存储"把磁盘占用压到极致。2020 年 yarn 2(berry)做 Plug’n’Play,零 node_modules。2025 年的新项目几乎只在这三者之间选。本文用一份"选型决策树"和"命令对照表"收尾。
适用读者:要给新项目选包管理器;被同事的 pnpm install 报错困扰;要从 yarn 1 升级到 berry/pnpm。
前置知识:用过 npm 即可。
目录
- 三巨头历史与设计哲学
- 安装与版本管理
- 命令对照速查表
- 配置文件:.npmrc / .yarnrc / .npmrc
- lockfile:依赖的"唯一真理"
- 镜像加速与代理
- 常见排错
- yarn berry / pnpm:升级路径与坑
- monorepo 工作区
- 选型决策树
1. 三巨头历史与设计哲学
| 工具 | 诞生 | 维护方 | 核心思路 |
|---|
| npm | 2010 | npm Inc.(被 GitHub 收购) | 官方默认,与 Node 同生共死 |
| yarn 1.x | 2016-10 | Facebook(已移交社区) | 并行安装 + lockfile + 离线缓存 |
| pnpm | 2017 | Zoltan Kochan | 硬链接 + 内容寻址,磁盘节约 50%+ |
| yarn berry (v2+) | 2020-01 | Yarn 团队 | PnP(Plug’n’Play)—— 抛弃 node_modules |
| bun | 2022 | Oven | Zig 编写的 JS 运行时 + 极快包管理器(兼容 npm script) |
核心设计差异:
- npm/yarn 1:每个项目的
node_modules 是平铺的"半 hoist"结构(依赖被提升到顶层 node_modules),节省空间但导致"幽灵依赖"——你能 require('xxx') 但它不在 package.json 里 - pnpm:用
.pnpm/<pkg>@<version>/node_modules/<pkg> 软链接隔离 + node_modules/<pkg> 软链接到 node_modules/.pnpm——真实依赖树且零幽灵依赖 - yarn berry PnP:根本不要
node_modules,每个包用 .pnp.cjs 文件描述"谁在哪"。启动快、装包快,但所有老工具链(webpack、jest、tsc)都得配 PnP 适配器
2. 安装与版本管理
2.1 npm
1
2
3
4
5
6
| node -v # Node 自带 npm
npm -v
# 升级
npm install -g npm@latest
npm update -g
|
2.2 yarn 1.x
1
2
3
4
5
6
7
8
9
10
| npm install -g yarn
yarn -v
# 升级
npm install -g yarn@latest
yarn self-update
# 装指定 tag
yarn self-update stable
yarn self-update canary
|
2.3 pnpm
1
2
3
4
5
6
7
8
| # 推荐用独立安装脚本(避免污染全局 npm)
curl -fsSL https://get.pnpm.io/install.sh | sh -
# Windows
iwr https://get.pnpm.io/install.ps1 -useb | iex
# 升级
pnpm self-update
|
2.4 bun(备选)
1
| curl -fsSL https://bun.sh/install | bash
|
3. 命令对照速查表
| 任务 | npm | yarn 1.x | pnpm |
|---|
| 初始化 | npm init | yarn init | pnpm init |
| 一键安装 | npm install | yarn / yarn install | pnpm install |
| 添加依赖 | npm i pkg | yarn add pkg | pnpm add pkg |
| 添加开发依赖 | npm i -D pkg | yarn add -D pkg | pnpm add -D pkg |
| 全局装 | npm i -g pkg | yarn global add pkg | pnpm add -g pkg |
| 卸载 | npm uninstall pkg | yarn remove pkg | pnpm remove pkg |
| 升级 | npm update | yarn upgrade | pnpm update |
| 跑 script | npm run dev | yarn dev | pnpm dev |
| 清理缓存 | npm cache clean --force | yarn cache clean | pnpm store prune |
| 列出已装 | npm list | yarn list | pnpm list |
| 装指定版本 | npm i pkg@1.2.3 | yarn add pkg@1.2.3 | pnpm add pkg@1.2.3 |
| 只装 prod | npm i --production | yarn install --production | pnpm install --prod |
| 锁文件 | package-lock.json | yarn.lock | pnpm-lock.yaml |
4. 配置文件
4.1 npm:.npmrc
放在项目根目录或 ~/.npmrc(用户级):
1
2
3
4
| registry=https://registry.npmmirror.com
save-exact=true
package-lock=true
audit=false
|
4.2 yarn 1:.yarnrc
1
| registry "https://registry.npmmirror.com"
|
4.3 pnpm:.npmrc(与 npm 兼容)+ pnpm-workspace.yaml
1
2
3
4
| # .npmrc
registry=https://registry.npmmirror.com
auto-install-peers=true # 自动装 peer dep
shamefully-hoist=true # 兼容老 webpack/jest
|
5. lockfile:依赖的"唯一真理"
Why lockfile 必交 Git:不交 lockfile,同一个 package.json 在不同时间/不同网络下能装出完全不同的依赖树——经典 bug"我这能跑你那不能跑"。
| 工具 | 文件名 | 是否必交 Git | 是否必交 lockfile 即可不重装 |
|---|
| npm 5+ | package-lock.json | ✅ | ✅ |
| yarn 1 | yarn.lock | ✅ | ✅ |
| pnpm | pnpm-lock.yaml | ✅ | ✅ |
| yarn berry | yarn.lock | ✅ | 配合 .yarn/cache |
1
2
3
4
| # 不读 lockfile 强制重装(出问题时偶尔用)
npm install --no-package-lock
yarn install --no-lockfile
pnpm install --no-lockfile
|
6. 镜像加速与代理
6.1 永久改源
1
2
3
| npm config set registry https://registry.npmmirror.com
yarn config set registry https://registry.npmmirror.com
pnpm config set registry https://registry.npmmirror.com
|
6.2 nrm 一键切换
1
2
3
4
5
6
7
8
9
| npm install -g nrm
nrm ls
# npm ---------- https://registry.npmjs.org/
# yarn --------- https://registry.yarnpkg.com/
# cnpm --------- https://r.cnpmjs.org/
# taobao ------- https://registry.npmmirror.com/
# tencent ------ https://mirrors.cloud.tencent.com/npm/
nrm use taobao
|
6.3 代理
1
2
3
4
5
6
7
| # 命令行(项目级)
npm config set proxy http://127.0.0.1:1081
npm config set https-proxy http://127.0.0.1:1081
# 环境变量(用户级)
export HTTP_PROXY=http://127.0.0.1:1081
export HTTPS_PROXY=http://127.0.0.1:1081
|
7. 常见排错
7.1 循环依赖错误
1
2
| npm error ERESOLVE could not resolve
npm error Conflicting peer dependency: react@17 vs react@18
|
解决:
1
2
3
| npm install xxx --legacy-peer-deps
yarn install --ignore-peer-deps
pnpm install --shamefully-hoist
|
7.2 GitHub SSH 权限问题
1
2
| npm ERR! code 128
npm ERR! git@github.com: Permission denied (publickey)
|
解决:
1
2
3
4
5
6
7
| # 1. 生成密钥
ssh-keygen -t ed25519 -C "<YOUR_EMAIL>"
# 2. 把 ~/.ssh/id_ed25519.pub 内容加到 GitHub → Settings → SSH and GPG keys
# 3. 验证
ssh -T git@github.com
|
7.3 浏览器数据自动更新错误
1
| browserslist: Caniuse was updated
|
说明 browserslist 数据陈旧。重新跑 npx update-browserslist-db@latest。
7.4 装包后跑起来报错
经典排错四件套:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 清缓存 + 删 node_modules
rm -rf node_modules package-lock.json
npm cache clean --force
npm install
# 2. 升 Node
nvm use 20
# 3. 检查 peer dep
npm ls react
# 4. 看完整错误
npm install --verbose 2>&1 | head -100
|
8. yarn berry / pnpm:升级路径与坑
8.1 yarn 1 → yarn berry (v2+)
1
2
3
4
5
6
| # 在项目目录
yarn set version berry
# → Saving the new release in .yarn/releases/yarn-3.2.1.cjs
yarn --version
# 3.2.1
|
升级后会自动生成:
1
2
3
4
5
6
7
| .yarn/
├── cache/ # 离线包缓存(建议 commit)
├── unplugged/ # 编译型 native 模块临时目录
└── releases/ # yarn 二进制(建议 commit)
.yarnrc.yml # 新版配置
.pnp.cjs # PnP 依赖图
|
.gitignore 必须加:
1
2
3
4
| /.pnp.*
.yarn/cache
.yarn/unplugged
.yarn/install-state.gz
|
设置 npm registry:
1
2
| yarn config set npmRegistryServer https://registry.npmmirror.com
yarn # 装包
|
8.2 yarn berry 的核心——PnP
PnP(Plug’n’Play)没有 node_modules!所有包都在 .yarn/cache/,启动时通过 .pnp.cjs 的"依赖图"直接定位文件。
优点:
- 装包速度比 yarn 1 快 2-3 倍
- 启动速度提升(无文件系统 IO)
- 杜绝幽灵依赖(包必须显式声明在 deps)
坑:
- 很多老工具链不兼容——必须用 PnP 适配版
- Webpack 4/5:装
@yarnpkg/pnpify 然后跑 yarn pnpify webpack - Jest:装
jest-pnp-resolver - TypeScript:配
pnpPlugin 解析器 - VSCode:装 ZipFS 扩展才能读
.yarn/cache
小贴士:Berry 默认是 PnP。如果你不想抛弃 node_modules,加 .yarnrc.yml:
1
| nodeLinker: node-modules
|
这样就像 yarn 1 一样工作,但还是 yarn berry 的安装器。
8.3 从 npm/yarn 1 迁到 pnpm
1
2
3
4
5
6
7
8
| # 1. 装 pnpm
npm install -g pnpm
# 2. 删除旧 lockfile
rm package-lock.json yarn.lock
# 3. 用 pnpm 装
pnpm install
|
坑:老项目里大量 require('xxx') 但 xxx 不在 package.json 的"幽灵依赖",pnpm 装完会报错。两种解法:
- 找到所有
require/import 实际用到的包,补到 package.json - 在
.npmrc 加 shamefully-hoist=true(pnpm 模拟 npm 的"提升"行为)——不推荐,治标不治本
9. monorepo 工作区
包管理工具的"工作区(workspace)“能力,让多个子包共享一个 node_modules,是 monorepo 的基石。
9.1 yarn 1 workspaces
package.json:
1
2
3
4
| {
"private": true,
"workspaces": ["packages/*", "apps/*"]
}
|
9.2 pnpm workspaces(最推荐)
pnpm-workspace.yaml:
1
2
3
| packages:
- 'apps/*'
- 'packages/*'
|
1
2
3
| pnpm -F <pkg> add <dep> # 只给某个子包加依赖
pnpm -r run build # 跑所有子包的 build
pnpm --filter <pkg>... <cmd> # 依赖关系图上跑命令
|
9.3 npm workspaces(7+ 支持)
package.json:
1
2
3
| {
"workspaces": ["packages/*"]
}
|
1
2
| npm install lodash -w @myorg/utils
npm run build --workspaces
|
monorepo 工具链对比:
| 工具 | 适合规模 | 特点 |
|---|
| npm/yarn/pnpm workspaces | 小团队 | 零额外依赖 |
| Lerna | 中型 | 已并入 Nx |
| Nx | 中大型 | 任务编排、缓存、依赖图分析最强 |
| Turborepo | 中大型 | 增量构建、远程缓存、零配置 |
| Rush | 大型 | 微软维护,企业级权限与发布流水线 |
10. 选型决策树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| 新项目选包管理器?
├─ 想 0 学习成本、纯前端小项目
│ └─ npm ✓
│
├─ monorepo + 多包共享
│ ├─ 小型(< 5 包)→ npm / yarn 1 workspaces
│ ├─ 中型(5-20 包)→ pnpm workspaces ✓
│ └─ 大型(> 20 包)→ pnpm + Turborepo / Nx
│
├─ 老 npm 项目维护,不想折腾
│ └─ npm 9+ ✓(性能已与 yarn 1 持平)
│
├─ 公司有私服(Nexus / Verdaccio)镜像严格
│ └─ pnpm(lockfile 完整,依赖提升少)
│
├─ 极致装包速度、CI 跑得快
│ └─ pnpm(内容寻址,复用 100%)
│
└─ 抛弃 node_modules、要 PnP 黑科技
└─ yarn berry (PnP) —— 仅当你团队接受重写所有工具链时
|
小贴士:CI 环境装包时,pnpm 默认不用网络——因为依赖都已"硬链接"到全局 store。建议在 package.json 旁边放 .npmrc:
1
| prefer-frozen-lockfile=true
|
锁文件没变就直接复用 node_modules,CI 启动时间从 90s 降到 15s。
小结
npm / yarn / pnpm / berry 的设计哲学各有不同。2025 年的"默认推荐"是 pnpm——装包快、磁盘省、workspace 体验最好、monorepo 生态最完整(Turbo/Nx 优先支持)。老项目维护用 npm 就好,不要为了追新而迁移。yarn berry 仅在大公司、复杂 monorepo、追求极致启动速度时考虑。
参考资料