一、Monorepo 适用前提
使用 Monorepo 组织代码的核心前提是:你拥有多个相关项目,且这些项目之间存在组件、逻辑或工具的复用需求。若每个项目完全独立、无任何共用代码,则无需使用 Monorepo 策略。
二、Monorepo 简介
Monorepo(Monolithic Repository,译为“单体仓库”)是一种代码管理策略,核心是将多个相关的项目、模块或应用的源代码,集中存储在单一版本控制仓库中;与之相对的是多仓库(Polyrepo/Multirepo)管理策略。
在 Monorepo 目录结构中,除了根目录会有统一的 package.json 管理公共依赖外,每个 sub-package(子包)下也会有自身专属的 package.json,用于管理该子包特有的依赖。兄弟模块之间可通过各自 package.json 中定义的 name 相互引用,既保证了模块独立性,又实现了资源共享。
Monorepo 核心特性
- 统一存储:所有相关项目的代码、配置文件及文档均位于同一仓库,便于整体管理与查阅。
- 共享资源:可共享依赖包、工具链、代码规范及 CI/CD 流程,减少重复配置,降低维护成本。
- 源码直接引用:跨项目依赖无需发布到包管理器,可通过本地路径直接引用,提升开发效率。
- 原子提交:跨多个项目的更改可在单个提交中完成,确保版本一致性,避免多仓库同步导致的冲突。
- 统一版本控制:所有代码共享一套分支、标签及版本历史,便于追溯变更、回滚版本。
Monorepo 优缺点
优点
- 保留多仓库(Multirepo)的核心优势:代码可复用、模块独立管理、业务场景分工明确、代码耦合度低。
- 版本管理更便捷一致,有效降低不同项目间的版本冲突风险。
- 可统一构建、部署流程,减少多项目配置与维护的工作量。
缺点
- 仓库体积易随时间增长变得庞大复杂,导致构建时间延长,
git clone、git pull等操作的成本增加。 - 权限管理精细化不足:难以实现项目粒度的精准权限控制,易出现非负责人误改代码的风险。
三、组件库项目选用 Monorepo 的原因
组件库项目天然适合采用 Monorepo 模式,核心原因是组件库需拆分多个功能模块,而 Monorepo 能实现模块的清晰划分与灵活复用。通常组件库会拆分以下模块:
components包:组件库核心,实现所有 UI 组件的核心逻辑与渲染效果。shared包:存放通用工具方法、辅助逻辑,供其他模块复用。theme包:实现组件库的主题样式定制、样式变量管理等功能。cli包:提供组件库模板脚手架的命令行工具,简化开发与使用流程。docs包:存放组件库的使用文档、示例 Demo,方便开发者查阅与参考。playground包:提供在线编辑、演示环境,便于开发者调试组件效果。
模块划分越清晰,复用的灵活性与可操作性越强,每个独立模块的产物体积也会更轻量,既便于维护,也能提升开发效率。
四、Monorepo 典型目录结构
# monorepo 标准目录结构(以多应用+多共享包为例)
monorepo-demo
├── apps/ # 应用程序目录(存放可独立部署的应用)
│ ├── web-app/ # Web 端应用
│ ├── mobile-app/ # 移动端应用
│ └── admin-panel/ # 管理后台应用
│
├── packages/ # 共享包目录(存放可复用的模块/工具)
│ ├─ module-a
│ │ ├─ src # 模块 a 的源码
│ │ ├─ node_modules # 模块 a 专属依赖(可选)
│ │ └─ package.json # 模块 a 的依赖配置
│ └─ module-b
│ ├─ src # 模块 b 的源码
│ └─ package.json # 模块 b 的依赖配置
│
├── .eslintrc # 全局配置文件(对所有子包/应用生效)
├── node_modules # 所有子包/应用共享的依赖
└── package.json # 全局依赖配置(private: true 避免误发布)
四、Monorepo 快速上手(基于 pnpm)
本教程基于 pnpm 实现 Monorepo 管理(pnpm 对 Monorepo 支持更友好,提供 workspace 功能简化多包管理),步骤清晰可落地,适合前端项目快速改造与搭建。
1. 前置准备:安装 pnpm
首先需全局安装 pnpm,用于执行 Monorepo 相关操作:
npm i pnpm -g # 全局安装 pnpm
pnpm -v # 验证安装成功(显示版本号即正常)
2. 现有项目改造(以 Vite + Vue3 项目为例)
假设现有一个独立的 Vite + Vue3 项目,目录结构如下:
web/ # 原独立项目根目录
├── index.html
├── package-lock.json
├── package.json
├── public/
├── README.md
├── src/
│ ├── App.vue
│ ├── components/
│ └── main.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
将其改造为 Monorepo 结构,步骤如下:
- 创建核心目录:在项目根目录(原 web 目录的上级)新建
apps(存放应用)和packages(存放共享包)目录,将原 web 项目作为子应用,移动到apps/web目录下(也可根据需求调整为packages/portal,视项目定位而定)。 - 移动项目文件:将原 web 目录下的所有文件(src、public、package.json 等),全部剪切到
apps/web目录中。 - 配置 workspace:在 Monorepo 根目录(apps 和 packages 的上级)创建
pnpm-workspace.yaml文件,指定多包路径。 - 创建全局 package.json:在 Monorepo 根目录创建
package.json,设置项目名称并标记为私有(避免误发布)。 - 安装依赖:在 Monorepo 根目录执行
pnpm i,统一安装所有子应用/子包的依赖。 - 测试启动:执行
pnpm -F web dev(-F 即 --filter,指定启动目标子应用),验证本地启动正常。 - 简化命令:在根目录 package.json 中配置 scripts 脚本,方便快速启动、构建项目。
3. 完整 Monorepo 目录结构(多应用+多共享包)
改造后,若项目包含多个应用(web、admin、api)和多个共享包,目录结构如下:
project/ # Monorepo 根目录
├─ apps/ # 应用目录(可独立部署)
│ ├─ web/ # 前台前端应用(原 Vite + Vue3 项目)
│ ├─ admin/ # 管理后台应用
│ └─ api/ # 后端应用(如 Node.js、Go 项目)
│
├─ packages/ # 共享包目录(可复用模块)
│ ├─ shared-types/ # 前端共享类型定义(TS 项目可选)
│ ├─ api-client/ # OpenAPI 生成的 TS SDK(可选)
│ └─ configs/ # 共享配置(eslint、tsconfig 等)
│
├─ pnpm-workspace.yaml # workspace 配置文件
├─ package.json # 全局依赖与脚本配置
└─ README.md # 项目说明文档
4. 关键配置文件编写
(1)pnpm-workspace.yaml
# 配置需要管理的子包/子应用路径,使用时去掉注释
packages:
- "apps/web" # 前台应用
- "apps/admin" # 管理后台应用
- "apps/mock-server" # 模拟服务(可选)
- "packages/*" # 所有共享包
# - "apps/api" # 若后端是 Node.js 项目,可添加此路径
(2)根目录 package.json
{
"name": "monorepo-demo", # 项目名称(自定义)
"private": true, # 标记为私有,禁止发布到 npm
"scripts": {
// 启动命令(简化子应用启动)
"dev:web": "pnpm --filter web dev",
"dev:admin": "pnpm --filter admin dev",
"dev:api": "cd apps/api && go run ./cmd/server", # 后端启动命令(示例)
"dev": "pnpm run dev:web", # 默认启动前台应用
// 构建命令
"build:web": "pnpm --filter web build",
"build:admin": "pnpm --filter admin build",
"build:api": "cd apps/api && go build -o ./bin/server ./cmd/server", # 后端构建(示例)
// 全局代码检查与测试
"lint": "pnpm -r lint", # 递归执行所有子包/应用的 lint 命令
"test": "pnpm -r test" # 递归执行所有子包/应用的 test 命令
}
}
5. 验证与测试
- 安装依赖:在 Monorepo 根目录执行
pnpm i,pnpm 会自动识别 workspace 中的所有子包/应用,统一管理依赖(避免重复安装)。 - 启动子应用:执行
pnpm dev:web,若能正常启动并访问页面,说明改造成功。 - 构建子应用:执行
pnpm build:web,检查构建产物是否正常生成。
6. 新增子应用(以 admin 为例)
- 创建应用:在
apps目录下执行以下命令,创建新的子应用(以 Vite 为例):npm create vite admin # 创建 admin 应用,选择所需框架(Vue/React 等) - 安装依赖:在 Monorepo 根目录执行
pnpm i,自动安装 admin 应用的依赖。 - 测试验证:执行
pnpm dev:admin启动应用,pnpm build:admin构建应用,确认正常运行。
7. 依赖管理
场景 1:安装公共依赖(所有子包共用)
公共依赖(如 lodash、axios)需安装到根目录,所有子包可直接使用,无需重复安装:
# -w 表示安装到根目录(workspace root)
pnpm install lodash -w
场景 2:安装局部依赖(仅某个子包使用)
若仅 apps/web 需要 echarts,可通过两种方式安装:
# 方式 1:进入子包目录安装
cd apps/web && pnpm install echarts
# 方式 2:任意目录下用 --filter 指定目标包
pnpm install echarts --filter web
场景 3:安装子包依赖(引用 workspace 内的共享包)
若 apps/web 需要引用 packages/ui-shared 这个共享包,需先给 ui-shared 的 package.json 定义唯一名称(如 @demo/ui-shared),再执行以下命令:
# --workspace 表示从本地 workspace 安装,而非 npm 仓库
pnpm install @demo/ui-shared --workspace --filter web
安装后,apps/web 的 package.json 会出现如下依赖:
{
"dependencies": {
"@demo/ui-shared": "workspace:^" // workspace: 表示本地复用
}
}
此时,apps/web 即可直接引用共享包的代码:
// apps/web/src/main.ts
import { Button } from '@demo/ui-shared';