Monorepo - pnpm Workspace
项目结构
my-monorepo/
├── pnpm-workspace.yaml # 工作区定义
├── package.json
├── packages/ # 公共包
│ ├── ui/ # UI 组件库
│ ├── utils/ # 工具函数
│ └── eslint-config/ # ESLint 共享配置
├── apps/ # 应用
│ ├── web/ # 前端应用
│ └── docs/ # 文档站点
└── .npmrcmy-monorepo/
├── pnpm-workspace.yaml # 工作区定义
├── package.json
├── packages/ # 公共包
│ ├── ui/ # UI 组件库
│ ├── utils/ # 工具函数
│ └── eslint-config/ # ESLint 共享配置
├── apps/ # 应用
│ ├── web/ # 前端应用
│ └── docs/ # 文档站点
└── .npmrc配置文件
pnpm-workspace.yaml
yaml
packages:
- 'apps/*'
- 'packages/*'packages:
- 'apps/*'
- 'packages/*'.npmrc
shamefully-hoist=true # 将依赖提升到 root node_modules(兼容某些工具)
strict-peer-dependencies=falseshamefully-hoist=true # 将依赖提升到 root node_modules(兼容某些工具)
strict-peer-dependencies=false常用命令
bash
# 在所有包中执行命令
pnpm -r run build
# 过滤特定包
pnpm --filter @my/ui run build
pnpm --filter "@my/*" run test
# 将 utils 安装到 web 应用的依赖中
pnpm --filter @my/web add @my/utils
# 所有包安装依赖
pnpm install# 在所有包中执行命令
pnpm -r run build
# 过滤特定包
pnpm --filter @my/ui run build
pnpm --filter "@my/*" run test
# 将 utils 安装到 web 应用的依赖中
pnpm --filter @my/web add @my/utils
# 所有包安装依赖
pnpm installworkspace 协议
json
// packages/ui/package.json
{
"name": "@my/ui",
"version": "1.0.0",
"exports": {
".": "./src/index.ts",
"./button": "./src/button/index.ts"
}
}
// apps/web/package.json(引用)
{
"dependencies": {
"@my/ui": "workspace:*", // 始终使用本地最新版本
"@my/utils": "workspace:^1.0.0" // 范围匹配
}
}// packages/ui/package.json
{
"name": "@my/ui",
"version": "1.0.0",
"exports": {
".": "./src/index.ts",
"./button": "./src/button/index.ts"
}
}
// apps/web/package.json(引用)
{
"dependencies": {
"@my/ui": "workspace:*", // 始终使用本地最新版本
"@my/utils": "workspace:^1.0.0" // 范围匹配
}
}发布时会自动将 workspace: 协议替换为实际版本号。
依赖管理策略
bash
# 公共依赖安装在 root(提升)
pnpm add -w typescript -D
# 特定包安装
pnpm --filter @my/web add react
# 更新所有包中的公共依赖
pnpm up -r typescript# 公共依赖安装在 root(提升)
pnpm add -w typescript -D
# 特定包安装
pnpm --filter @my/web add react
# 更新所有包中的公共依赖
pnpm up -r typescriptnode_modules 结构
node_modules/
├── .pnpm/ # 内容寻址存储,所有依赖实际存放在这里
├── react -> .pnpm/react@18.2.0/node_modules/react
├── vue -> .pnpm/vue@3.4.0/node_modules/vue
├── @my/
│ ├── ui -> ../../packages/ui # 软链接到本地包
│ └── utils -> ../../packages/utilsnode_modules/
├── .pnpm/ # 内容寻址存储,所有依赖实际存放在这里
├── react -> .pnpm/react@18.2.0/node_modules/react
├── vue -> .pnpm/vue@3.4.0/node_modules/vue
├── @my/
│ ├── ui -> ../../packages/ui # 软链接到本地包
│ └── utils -> ../../packages/utilspnpm 通过硬链接 + 软链接实现:
- 相同版本依赖硬盘只存一份(硬链接)
workspace:协议包通过软链链接- 避免了 npm/yarn 的幽灵依赖问题
幽灵依赖 (Phantom Dependency)
js
// ❌ npm/yarn 中可以直接引用未声明的依赖
// web 的 package.json 没有声明 lodash,但 web 代码可以 import
// 因为 lodash 被某个间接依赖安装并提升了
// ✅ pnpm 严格模式,未声明的包不可访问
// 必须在 web/package.json 中显式声明
{
"dependencies": {
"lodash": "^4.0.0" // 必须显式添加
}
}// ❌ npm/yarn 中可以直接引用未声明的依赖
// web 的 package.json 没有声明 lodash,但 web 代码可以 import
// 因为 lodash 被某个间接依赖安装并提升了
// ✅ pnpm 严格模式,未声明的包不可访问
// 必须在 web/package.json 中显式声明
{
"dependencies": {
"lodash": "^4.0.0" // 必须显式添加
}
}
Cao Note