第一部分:Monorepo 基础认知与环境准备
1.1 什么是 Monorepo?
核心概念:将多个相关项目存放在同一个代码仓库中的代码管理策略
优势:
- 代码共享更便捷(组件/工具函数复用)
- 依赖管理更统一(避免版本碎片化)
- 跨项目修改更安全(原子提交)
- CI/CD 流程更高效(统一构建部署)
1.2 工具选择
为什么用 pnpm:
- 硬链接机制节省磁盘空间
- 依赖提升加速安装速度
- 完美支持 workspace 特性
环境准备:
# 安装 Node.js(>=16.17)
node -v
# 安装 pnpm
npm install -g pnpm
pnpm -v # 确认版本 >=7.0.0
1.3 初始化项目结构
mkdir monorepo-demo && cd monorepo-demo
pnpm init # 生成根 package.json
基础目录结构:
├── package.json
├── pnpm-workspace.yaml # 工作区配置文件
├── packages/ # 子包目录
│ ├── core/ # 核心工具库
│ └── web-app/ # 前端应用
└── apps/ # 应用层目录(可选)
第二部分:配置 Workspace 与创建子包
2.1 创建 workspace 配置文件
在项目根目录创建 pnpm-workspace.yaml
:
packages:
- "packages/*" # 必选:核心包目录
- "apps/*" # 可选:应用目录(如前端/后端)
2.2 创建第一个子包(示例:工具库)
mkdir -p packages/core
cd packages/core
pnpm init # 生成子包 package.json
修改子包配置(示例):
// packages/core/package.json
{
"name": "@demo/core", // 使用命名空间防止冲突
"version": "1.0.0",
"main": "src/index.ts",
"scripts": {
"build": "tsc"
}
}
2.3 创建第二个子包(示例:前端应用)
mkdir -p apps/web-app
cd apps/web-app
pnpm init # 生成应用 package.json
修改应用配置(示例):
// apps/web-app/package.json
{
"name": "@demo/web-app",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
2.4 安装公共依赖(所有子包共享)
# 在根目录操作
pnpm add typescript -D -w # -w 表示安装在根目录(公共依赖)
2.5 安装子包专属依赖
# 为 web-app 安装专属依赖
pnpm add vite react react-dom --filter @demo/web-app
# 为 core 安装专属依赖
pnpm add lodash @types/lodash --filter @demo/core
2.6 子包之间相互引用
# 让 web-app 使用 core 包
pnpm add @demo/core --filter @demo/web-app --workspace
此时依赖关系会被自动处理:
// apps/web-app/package.json
{
"dependencies": {
"@demo/core": "workspace:*" # 自动生成的本地链接
}
}
2.7 根目录脚本配置(可选)
// package.json
{
"scripts": {
"install:all": "pnpm install",
"dev": "pnpm --filter @demo/web-app dev",
"build": "pnpm -r run build" # -r 表示递归执行所有子包中的 build 命令
}
}
关键配置说明
--filter
参数:精准控制操作目标子包workspace:*
:表示使用本地工作区最新版本-w
:强制安装在根目录(公共开发依赖)-r
:递归执行所有子包中的命令
第三部分:工程化配置(TypeScript + ESLint)
3.1 配置根目录 TypeScript
# 根目录安装公共 TS 配置
pnpm add typescript @types/node -D -w
创建根目录 tsconfig.base.json
:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@demo/*": ["packages/*/src"] // 路径别名配置
}
},
"exclude": ["node_modules"]
}
3.2 子包继承 TS 配置
示例:core 包配置
// packages/core/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
}
}
3.3 配置统一 ESLint
# 根目录安装 ESLint 全家桶
pnpm add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -D -w
创建 .eslintrc.cjs
:
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
// 自定义规则
}
}
3.4 配置 Vite 应用(示例)
# 为 web-app 安装 Vite 相关依赖
pnpm add vite @vitejs/plugin-react @types/react @types/react-dom --filter @demo/web-app
创建 apps/web-app/vite.config.ts
:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@demo/core': '../../packages/core/src' // 手动配置路径映射
}
}
})
关键技巧说明
- 路径别名:通过
tsconfig.paths
+vite.config.alias
实现跨包引用 - 配置继承:子包通过
extends
复用根配置,避免重复 - 统一规范:ESLint 在根目录集中管理代码风格
第四部分:依赖管理高级技巧
4.1 依赖类型区分
# 公共开发依赖(所有子包共享)
pnpm add jest @types/jest -D -w
# 指定子包生产依赖
pnpm add axios --filter @demo/web-app
# 指定子包开发依赖
pnpm add @testing-library/react -D --filter @demo/web-app
4.2 依赖提升控制
# 禁止提升某些依赖(如冲突的版本)
.npmrc 添加配置:
hoist=false # 完全禁用提升
hoist-pattern[]=*react* # 仅提升包含 react 的依赖
4.3 版本一致性强制
# 在根 package.json 添加:
"pnpm": {
"overrides": {
"react": "18.2.0", // 强制统一版本
"typescript": "^5.0.0"
}
}
第五部分:完整开发流程演示
5.1 启动开发环境
# 并行启动多个子包
pnpm --filter "@demo/web-app" dev
5.2 执行批量构建
# 按拓扑顺序构建(需安装 @pnpm/sort-packages)
pnpm -r --stream exec pnpm build
5.3 发布私有包
# 修改 core 包版本号
cd packages/core
pnpm version patch
# 打包并发布
pnpm build
pnpm publish --access public
第六部分:构建优化与任务管理
6.1 并行任务执行
# 并行执行所有子包的 build 命令
pnpm -r --parallel run build
# 过滤特定目录的任务
pnpm --filter "./packages/**" run test
6.2 缓存策略(以 Vite 为例)
// apps/web-app/vite.config.ts
export default defineConfig({
cacheDir: "../../.vite", // 共享缓存目录
build: {
outDir: "../../dist/web-app" // 统一输出目录
}
})
6.3 跨包任务依赖(拓扑排序)
# 安装拓扑排序工具
pnpm add @pnpm/sort-packages -D -w
# 按依赖顺序执行构建
pnpm -r --sort exec pnpm build
第七部分:CI/CD 集成示例
7.1 GitHub Actions 基础配置
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- run: pnpm install
- run: pnpm build
7.2 按需触发构建
# 仅当 packages/core 发生变更时触发
paths:
- "packages/core/**"
第八部分:部署策略
8.1 静态资源部署(前端应用)
# 示例:部署到 AWS S3
pnpm --filter @demo/web-app exec aws s3 sync ./dist s3://your-bucket
8.2 NPM 私有包部署
# 1. 在根目录配置 .npmrc
@demo:registry=https://your-private-registry
# 2. 发布命令
pnpm --filter @demo/core publish --access public
第九部分:常见问题排查
9.1 幽灵依赖(Phantom Dependencies)
现象:报错 Module not found
解决:
# 检查依赖来源
pnpm why <package-name>
# 解决方案:显式声明缺失依赖
9.2 路径别名失效
现象:TS 编译成功但运行时报错
解决:
- 确认
tsconfig.paths
配置 - 检查构建工具(Vite/Webpack)的别名配置
- 对于 Jest 需额外配置 moduleNameMapper
9.3 构建顺序问题
现象:依赖包未优先构建
解决:
# 显式指定构建顺序
pnpm --filter @demo/core run build && pnpm --filter @demo/web-app run build
第十部分:维护建议
10.1 目录结构规范
monorepo-demo/
├── .github/ # CI/CD 配置
├── .husky/ # Git hooks
├── .vscode/ # 团队统一编辑器配置
├── configs/ # 共享配置(ESLint/Jest)
│ ├── eslint-base.cjs
│ └── jest-config.js
├── packages/
│ ├── core/ # 基础库
│ ├── utils/ # 工具函数
│ └── ui/ # 组件库
└── apps/
├── web-app/ # 前端应用
└── admin/ # 管理后台
10.2 变更监控工具
# 安装变更集管理工具
pnpm add @changesets/cli -D -w
pnpm changeset init
下期预告:
实战案例:完整 Monorepo 项目演示
场景描述
构建包含以下模块的 Monorepo:
@demo/ui
: Vue 组件库@demo/utils
: 工具函数库@demo/web
: 消费者端应用@demo/admin
: 管理后台应用
关键命令示例
# 同时启动两个前端应用
pnpm --filter "{@demo/web,@demo/admin}" dev
# 仅更新 UI 库时触发相关构建
pnpm --filter @demo/ui... run build