Nodejs
Monorepo
在单一代码仓库中管理多个子项目
pnpm Workspaces 允许在单个代码仓库中管理多个子项目,子项目之间可以直接互相引用,共享依赖安装在根目录,避免重复下载。
适合以下场景:
- 多个应用共用同一套 UI 组件或业务逻辑
- 开发工具库时,将核心代码、测试、示例和文档统一管理
目录结构
my-monorepo/
├── apps/
│ ├── web/ # 应用 A
│ └── mobile/ # 应用 B
├── packages/
│ ├── ui/ # 共享 UI 组件
│ └── utils/ # 共享工具函数
├── pnpm-workspace.yaml
└── package.json搭建步骤
1. 创建 pnpm-workspace.yaml
在根目录创建 pnpm-workspace.yaml,声明哪些目录是工作区包:
packages:
- 'apps/**'
- 'packages/**'2. 配置根目录 package.json
根目录的 package.json 通常只管理公共开发工具,不包含实际业务代码:
{
"private": true,
"scripts": {
"dev": "pnpm -r run dev",
"build": "pnpm -r run build"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}3. 为每个包设置 package.json
每个子包需要有自己的 package.json,name 字段作为包名用于相互引用:
{
"name": "@my-app/ui",
"version": "1.0.0",
"main": "./src/index.ts"
}4. 在应用中引用本地包
在应用的 package.json 中,用 workspace:* 协议声明对本地包的依赖:
{
"name": "@my-app/web",
"dependencies": {
"@my-app/ui": "workspace:*",
"@my-app/utils": "workspace:*"
}
}运行 pnpm install 后,pnpm 会自动将本地包通过符号链接安装,修改包源码后无需重新安装。
5. 配置 TypeScript 路径(可选)
如果使用 TypeScript,在根目录的 tsconfig.json 中配置 paths,让编辑器正确解析本地包的类型:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@my-app/*": ["packages/*/src/index.ts"]
}
}
}每个子包的 tsconfig.json 继承根配置:
{
"extends": "../../tsconfig.json",
"include": ["src"]
}常用命令
| 命令 | 说明 |
|---|---|
pnpm install | 安装所有包的依赖 |
pnpm -r run build | 在所有包中运行 build 脚本 |
pnpm --filter @my-app/web dev | 只在指定包中运行脚本 |
pnpm --filter @my-app/web add lodash | 只为指定包安装依赖 |
pnpm add -w typescript | 在根目录安装依赖(-w 表示 workspace root) |
注意事项
幽灵依赖问题
pnpm 默认不提升依赖,子包只能访问自己 package.json 中声明的依赖。如果某个包 A 依赖了 B,而 B 依赖了 C,A 不能直接导入 C,即使 C 已经安装在 node_modules 中。
遇到此类导入失败时,有两个解决方案:
方案一(推荐):在报错的包中显式声明缺失的依赖:
{
"dependencies": {
"c-package": "^1.0.0"
}
}方案二:在根目录创建 .npmrc 开启依赖提升(会丧失 pnpm 的隔离优势):
shamefully-hoist=true