📦 pnpm 使用教程 — Node.js 包管理终极指南

pnpm 是当前最快的 Node.js 包管理器,采用内容寻址存储,比 npm/yarn 更快、更省磁盘空间。它严格管理依赖关系,消除幽灵依赖问题,原生支持 monorepo workspace,是现代 Node.js 项目的最佳选择。

一、安装 pnpm

# 官方脚本(推荐)
curl -fsSL https://get.pnpm.io/install.sh | sh -

# 通过 npm
npm install -g pnpm

# 通过 Homebrew (macOS)
brew install pnpm

# 通过 Corepack (Node.js 16.13+)
corepack enable
corepack prepare pnpm@latest --activate

# 验证安装
pnpm --version
💡 推荐 Corepack,团队协作时自动管理版本,避免 lockfile 冲突。

二、为什么选择 pnpm?

vs npm / yarn

特性npmyarnpnpm
安装速度中等最快
磁盘占用极低
node_modules扁平扁平严格嵌套
幽灵依赖存在存在不存在
Monorepo一般最佳

内容寻址存储

pnpm 将包缓存到全局 store,通过硬链接引用而非复制。100 个项目用 lodash 只占 1 份空间。

严格的 node_modules 结构

node_modules/
├── .pnpm/              # 所有依赖的硬链接存储
├── lodash -> .pnpm/lodash@4.17.21/...
├── express -> .pnpm/express@4.18.2/...
└── your-dependency -> ...

你的代码只能访问直接声明的依赖,彻底消灭幽灵依赖


三、基本命令

# 初始化项目
pnpm init
pnpm create vite my-app --template react-ts

# 安装依赖
pnpm install          # 安装全部依赖
pnpm i                # 缩写

# 添加依赖
pnpm add express                   # 生产依赖
pnpm add -D typescript eslint      # 开发依赖
pnpm add react@18.2.0              # 指定版本
pnpm add "express@^4.18.0"         # 版本范围
pnpm add -g pm2                    # 全局安装

# 移除依赖
pnpm remove express
pnpm rm lodash

四、版本指定符与依赖类型

版本指定符

pnpm add express@4.18.2        # 精确版本
pnpm add "express@^4.18.0"     # 兼容: >=4.18.0 <5.0.0
pnpm add "express@~4.18.0"     # 最小: >=4.18.0 <4.19.0
pnpm add next@canary            # 发布渠道

依赖类型

{
  "dependencies": { "express": "^4.18.0" },
  "devDependencies": { "typescript": "^5.0.0" },
  "optionalDependencies": { "fsevents": "^2.3.0" },
  "peerDependencies": { "react": "^18.0.0" }
}
pnpm add express              # dependencies
pnpm add -D typescript        # devDependencies
pnpm add -O fsevents          # optionalDependencies
pnpm install --prod           # 跳过 dev 依赖
💡 所有 @types/*、测试框架、lint 工具用 -D 放进 devDependencies。

五、Workspace / Monorepo

pnpm-workspace.yaml

packages:
  - "packages/*"
  - "apps/*"

Workspace 协议

{
  "dependencies": {
    "@myorg/shared": "workspace:*",
    "@myorg/utils": "workspace:^1.0.0"
  }
}

根 package.json

{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "build": "pnpm -r run build",
    "dev:web": "pnpm --filter web dev"
  }
}
💡 monorepo 推荐:包名用 @org/ 前缀,workspace:* 引用,根目录统一管理 dev 依赖。

六、脚本与生命周期钩子

{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "test": "vitest",
    "lint": "eslint .",
    "prebuild": "pnpm lint",
    "postbuild": "echo Done",
    "prepare": "husky install"
  }
}
pnpm build              # 运行脚本(run 可省略)
pnpm test -- --watch    # 传递参数
pnpm exec eslint .      # 执行 bin 命令
pnpm dlx create-vite    # 临时执行(类似 npx)

七、pnpm Store 管理

pnpm store path      # 查看 store 路径: ~/.local/share/pnpm/store/v3
pnpm store prune     # 清理不再使用的包
pnpm store status    # 查看 store 状态
# .npmrc - 自定义 store 路径
store-dir = /data/pnpm-store

八、Lockfile (pnpm-lock.yaml)

  • 记录精确依赖树、版本、完整性校验
  • 跨平台兼容
  • 必须提交到 Git
pnpm install                    # 自动更新 lockfile
pnpm install --frozen-lockfile  # CI 中验证一致性
pnpm install --lockfile-only    # 仅更新 lockfile

九、.npmrc 配置

# .npmrc
registry=https://registry.npmmirror.com/
strict-peer-dependencies=true
auto-install-peers=true
save-workspace-protocol=true
enable-pre-post-scripts=true
// package.json 中的 pnpm 配置
{
  "pnpm": {
    "overrides": { "semver": "^7.5.0" },
    "patchedDependencies": {},
    "peerDependencyRules": {
      "ignoreMissing": ["@babel/*"]
    }
  }
}

十、过滤与跨包运行

# 特定包
pnpm --filter web dev
pnpm --filter "@myorg/*" build

# 依赖拓扑
pnpm --filter ...web build    # web 及其依赖
pnpm --filter web... build    # web 及依赖 web 的包

# 递归执行
pnpm -r build                 # 所有包
pnpm -r --workspace-concurrency=4 build  # 并行
pnpm -r exec rm -rf dist      # 执行任意命令

十一、CI/CD(GitHub Actions)

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: "pnpm"
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm test
      - run: pnpm build
// package.json 锁定版本
{ "packageManager": "pnpm@10.4.1" }

十二、Docker 多阶段构建

FROM node:20-alpine AS base
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app

FROM base AS deps
COPY pnpm-lock.yaml package.json ./
RUN pnpm install --frozen-lockfile

FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/pnpm-lock.yaml /app/package.json ./
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm install --prod
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]
💡 先复制 lockfile + package.json 安装依赖,利用 Docker 缓存层。

十三、从 npm/yarn 迁移

# 删除旧文件
rm -rf node_modules package-lock.json  # 或 yarn.lock

# pnpm 安装
pnpm install

# 处理幽灵依赖问题:显式添加缺失的包
pnpm add <缺失的包>
# 而不是开启 shamefully-hoist(不推荐)

十四、npm 生态兼容

overrides(解决版本冲突/安全漏洞)

{
  "pnpm": {
    "overrides": {
      "semver": "^7.5.0",
      "express>@types/node": "^20.0.0"
    }
  }
}

patch(修补第三方包)

pnpm patch some-package@1.0.0    # 创建补丁
# 编辑文件后
pnpm patch-commit <tmp目录>       # 保存补丁

十五、常用技巧

# 查看依赖
pnpm ls                       # 直接依赖
pnpm ls --depth=10            # 所有嵌套依赖
pnpm outdated                 # 过期依赖
pnpm up -i                    # 交互式更新

# 安全审计
pnpm audit
pnpm audit --fix

# 清理
pnpm store prune              # 清理 store
rm -rf node_modules pnpm-lock.yaml && pnpm install  # 完全重装

# 发布
pnpm publish
pnpm --filter @myorg/shared publish

实用别名(.zshrc / .bashrc)

alias pi="pnpm install"
alias pa="pnpm add"
alias pad="pnpm add -D"
alias pb="pnpm build"
alias pd="pnpm dev"

十六、命令速查表

命令说明
pnpm init初始化项目
pnpm i安装依赖
pnpm add <pkg>添加依赖
pnpm add -D <pkg>添加开发依赖
pnpm rm <pkg>移除依赖
pnpm up更新依赖
pnpm run <script>运行脚本
pnpm exec <cmd>执行命令
pnpm dlx <cmd>临时执行(npx)
pnpm -r <script>递归运行
pnpm --filter <pkg>过滤运行
pnpm store prune清理 store
pnpm ls查看依赖
pnpm audit安全审计
pnpm publish发布包

十七、常见问题

Q: pnpm 和 npm 的区别是什么?
pnpm 更快、更省空间,严格 node_modules 消除幽灵依赖,原生 monorepo 支持。

Q: 幽灵依赖问题怎么解决?
显式添加缺失的包到 package.json,而不是开启 shamefully-hoist。

Q: pnpm-lock.yaml 冲突怎么办?
直接运行 pnpm install 自动合并,失败则删除 lockfile 重新生成。

Q: 如何在 CI 中加速?
使用 cache: "pnpm" + --frozen-lockfile

Q: monorepo 中如何共享配置?
将通用 dev 依赖(@types、eslint、typescript)放在根目录。


参考链接