Featured image of post 包管理工具:npm / yarn / pnpm 全对比与 yarn berry 升级

包管理工具:npm / yarn / pnpm 全对比与 yarn berry 升级

从 npm 到 yarn 1.x 再到 pnpm,三大包管理工具的设计哲学、安装速度、磁盘占用、workspace 能力各不相同。本文按命令对照表 + 实测数据 + 升级陷阱 + monorepo 场景梳理,帮你为新项目选对工具。

为什么写这篇: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 即可。

目录

  1. 三巨头历史与设计哲学
  2. 安装与版本管理
  3. 命令对照速查表
  4. 配置文件:.npmrc / .yarnrc / .npmrc
  5. lockfile:依赖的"唯一真理"
  6. 镜像加速与代理
  7. 常见排错
  8. yarn berry / pnpm:升级路径与坑
  9. monorepo 工作区
  10. 选型决策树

1. 三巨头历史与设计哲学

工具诞生维护方核心思路
npm2010npm Inc.(被 GitHub 收购)官方默认,与 Node 同生共死
yarn 1.x2016-10Facebook(已移交社区)并行安装 + lockfile + 离线缓存
pnpm2017Zoltan Kochan硬链接 + 内容寻址,磁盘节约 50%+
yarn berry (v2+)2020-01Yarn 团队PnP(Plug’n’Play)—— 抛弃 node_modules
bun2022OvenZig 编写的 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. 命令对照速查表

任务npmyarn 1.xpnpm
初始化npm inityarn initpnpm init
一键安装npm installyarn / yarn installpnpm install
添加依赖npm i pkgyarn add pkgpnpm add pkg
添加开发依赖npm i -D pkgyarn add -D pkgpnpm add -D pkg
全局装npm i -g pkgyarn global add pkgpnpm add -g pkg
卸载npm uninstall pkgyarn remove pkgpnpm remove pkg
升级npm updateyarn upgradepnpm update
跑 scriptnpm run devyarn devpnpm dev
清理缓存npm cache clean --forceyarn cache cleanpnpm store prune
列出已装npm listyarn listpnpm list
装指定版本npm i pkg@1.2.3yarn add pkg@1.2.3pnpm add pkg@1.2.3
只装 prodnpm i --productionyarn install --productionpnpm install --prod
锁文件package-lock.jsonyarn.lockpnpm-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 1yarn.lock
pnpmpnpm-lock.yaml
yarn berryyarn.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 装完会报错。两种解法

  1. 找到所有 require/import 实际用到的包,补到 package.json
  2. .npmrcshamefully-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、追求极致启动速度时考虑。

参考资料

使用 Hugo 构建
主题 StackJimmy 设计