Monorepo 详解与快速上手指南

86 阅读8分钟

一、Monorepo 适用前提

使用 Monorepo 组织代码的核心前提是:你拥有多个相关项目,且这些项目之间存在组件、逻辑或工具的复用需求。若每个项目完全独立、无任何共用代码,则无需使用 Monorepo 策略。

二、Monorepo 简介

Monorepo(Monolithic Repository,译为“单体仓库”)是一种代码管理策略,核心是将多个相关的项目、模块或应用的源代码,集中存储在单一版本控制仓库中;与之相对的是多仓库(Polyrepo/Multirepo)管理策略。

在 Monorepo 目录结构中,除了根目录会有统一的 package.json 管理公共依赖外,每个 sub-package(子包)下也会有自身专属的 package.json,用于管理该子包特有的依赖。兄弟模块之间可通过各自 package.json 中定义的 name 相互引用,既保证了模块独立性,又实现了资源共享。

Monorepo 核心特性

2.png

  • 统一存储:所有相关项目的代码、配置文件及文档均位于同一仓库,便于整体管理与查阅。
  • 共享资源:可共享依赖包、工具链、代码规范及 CI/CD 流程,减少重复配置,降低维护成本。
  • 源码直接引用:跨项目依赖无需发布到包管理器,可通过本地路径直接引用,提升开发效率。
  • 原子提交:跨多个项目的更改可在单个提交中完成,确保版本一致性,避免多仓库同步导致的冲突。
  • 统一版本控制:所有代码共享一套分支、标签及版本历史,便于追溯变更、回滚版本。

Monorepo 优缺点

优点

  • 保留多仓库(Multirepo)的核心优势:代码可复用、模块独立管理、业务场景分工明确、代码耦合度低。
  • 版本管理更便捷一致,有效降低不同项目间的版本冲突风险。
  • 可统一构建、部署流程,减少多项目配置与维护的工作量。

缺点

  • 仓库体积易随时间增长变得庞大复杂,导致构建时间延长,git clonegit pull 等操作的成本增加。
  • 权限管理精细化不足:难以实现项目粒度的精准权限控制,易出现非负责人误改代码的风险。

三、组件库项目选用 Monorepo 的原因

组件库项目天然适合采用 Monorepo 模式,核心原因是组件库需拆分多个功能模块,而 Monorepo 能实现模块的清晰划分与灵活复用。通常组件库会拆分以下模块:

  • components 包:组件库核心,实现所有 UI 组件的核心逻辑与渲染效果。
  • shared 包:存放通用工具方法、辅助逻辑,供其他模块复用。
  • theme 包:实现组件库的主题样式定制、样式变量管理等功能。
  • cli 包:提供组件库模板脚手架的命令行工具,简化开发与使用流程。
  • docs 包:存放组件库的使用文档、示例 Demo,方便开发者查阅与参考。
  • playground 包:提供在线编辑、演示环境,便于开发者调试组件效果。

模块划分越清晰,复用的灵活性与可操作性越强,每个独立模块的产物体积也会更轻量,既便于维护,也能提升开发效率。

四、Monorepo 典型目录结构

1.png

# 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 结构,步骤如下:

  1. 创建核心目录:在项目根目录(原 web 目录的上级)新建 apps(存放应用)和 packages(存放共享包)目录,将原 web 项目作为子应用,移动到 apps/web 目录下(也可根据需求调整为packages/portal,视项目定位而定)。
  2. 移动项目文件:将原 web 目录下的所有文件(src、public、package.json 等),全部剪切到 apps/web 目录中。
  3. 配置 workspace:在 Monorepo 根目录(apps 和 packages 的上级)创建 pnpm-workspace.yaml 文件,指定多包路径。
  4. 创建全局 package.json:在 Monorepo 根目录创建 package.json,设置项目名称并标记为私有(避免误发布)。
  5. 安装依赖:在 Monorepo 根目录执行 pnpm i,统一安装所有子应用/子包的依赖。
  6. 测试启动:执行 pnpm -F web dev(-F 即 --filter,指定启动目标子应用),验证本地启动正常。
  7. 简化命令:在根目录 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. 验证与测试

  1. 安装依赖:在 Monorepo 根目录执行 pnpm i,pnpm 会自动识别 workspace 中的所有子包/应用,统一管理依赖(避免重复安装)。
  2. 启动子应用:执行 pnpm dev:web,若能正常启动并访问页面,说明改造成功。
  3. 构建子应用:执行 pnpm build:web,检查构建产物是否正常生成。

6. 新增子应用(以 admin 为例)

  1. 创建应用:在 apps 目录下执行以下命令,创建新的子应用(以 Vite 为例): npm create vite admin # 创建 admin 应用,选择所需框架(Vue/React 等)
  2. 安装依赖:在 Monorepo 根目录执行 pnpm i,自动安装 admin 应用的依赖。
  3. 测试验证:执行 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-sharedpackage.json 定义唯一名称(如 @demo/ui-shared),再执行以下命令:

# --workspace 表示从本地 workspace 安装,而非 npm 仓库
pnpm install @demo/ui-shared --workspace --filter web

安装后,apps/webpackage.json 会出现如下依赖:

{
  "dependencies": {
    "@demo/ui-shared": "workspace:^" // workspace: 表示本地复用
  }
}

此时,apps/web 即可直接引用共享包的代码:

// apps/web/src/main.ts
import { Button } from '@demo/ui-shared';