🧱 前端 Monorepo 完整入门指南

0 阅读4分钟

🧱 前端 Monorepo 完整入门指南

✅ 从零开始搭建一个包含两个 Web 应用 + 共享组件库的 Monorepo
✅ 适用于个人项目、开源库、中小型团队
✅ 技术栈:pnpm + Vite + Turborepo + React


一、为什么使用 Monorepo?

🎯 场景:我们的 Demo 项目

假设你正在开发:

  • web-a:一个商品展示页(如电商首页)
  • web-b:一个用户中心页(如个人资料)
  • 两者都用到了相同的按钮、弹窗、API 工具函数

❌ 如果用两个独立仓库(Multirepo):

  • 每次改一个公共按钮,要:

    1. shared-ui 仓库提交
    2. 发布新 npm 包(如 v1.0.1
    3. 分别在 web-aweb-b 中升级依赖
    4. 测试是否兼容
  • 容易出现 “A 用了 v1.0.0,B 用了 v1.0.1” 的版本碎片问题

✅ 使用 Monorepo 后:

  • 所有代码在一个仓库
  • 公共组件直接引用源码,修改立即生效
  • 一次提交可同时更新组件库 + 两个应用(原子变更)
  • 统一 ESLint、TypeScript、构建配置

💡 Monorepo 的核心价值提升紧密耦合项目的协作效率与工程一致性


二、Monorepo 背后的关键技术(结合 Demo 解释)

我们的 Demo 项目用到以下工具,它们各司其职:

技术作用在 Demo 中的角色
pnpm高效包管理器管理 web-aweb-bui 三个子项目的依赖,并自动链接内部包
pnpm workspaces工作区支持告诉 pnpm:“这三个目录属于同一个项目,请把 @my-monorepo/ui 当成本地包处理”
Turborepo任务调度与缓存pnpm build 只构建变更过的应用,第二次构建快 10 倍
Vite构建工具web-aweb-b 提供极速开发服务器和生产构建

下面逐一说明:


🔧 1. pnpm:更快更省的包管理器

  • 问题:npm/yarn 会为每个项目复制一份依赖,磁盘占用大

  • pnpm 方案:用硬链接 + 符号链接,所有项目共享同一份依赖存储

  • Demo 中

    • web-aweb-b 都用 React 18
    • pnpm 只下载一次 React,两个应用共用 → 节省 50%+ 磁盘空间

📦 2. pnpm Workspaces:本地包的“身份证”

  • 问题:如何让 web-a 知道 @my-monorepo/ui 不是 npm 包,而是本地代码?

  • 解决方案:通过 pnpm-workspace.yaml 声明工作区范围

  • Demo 中

    # pnpm-workspace.yaml
    packages:
      - 'apps/*'      # web-a, web-b
      - 'packages/*'  # ui
    

    → pnpm 自动将 packages/ui 视为合法包,无需发布到 npm


⚡ 3. Turborepo:智能任务调度器

  • 问题:每次改一行代码,都要重新构建两个应用?太慢!

  • Turborepo 方案

    • 分析依赖图(web-a 依赖 ui
    • 只重新构建受影响的部分
    • 缓存构建结果(本地 + 远程)
  • Demo 中

    • 修改 ui → 自动重建 web-aweb-b
    • 修改 web-a → 只重建 web-a
    • 第二次运行 pnpm build → 几乎瞬间完成(命中缓存)

🛠️ 4. Vite:现代化构建工具

  • 作用:提供开发服务器(dev)和生产构建(build

  • Demo 中

    • web-aweb-b 各自独立构建为静态文件(dist/ 目录)
    • 支持 React Fast Refresh(保存即更新)
    • 构建产物可直接部署到 GitHub Pages / Vercel

✅ 这些工具组合起来,构成了 轻量、高效、易维护的现代 Monorepo


三、从零搭建 Monorepo(完整步骤)

步骤 1:创建根目录

mkdir my-web-monorepo
cd my-web-monorepo
git init

步骤 2:初始化根 package.json

pnpm init

编辑为:

{
  "name": "my-web-monorepo",
  "version": "1.0.0",
  "private": true
}

步骤 3:启用 Workspaces

创建 pnpm-workspace.yaml

packages:
  - 'apps/*'
  - 'packages/*'

步骤 4:创建两个应用

pnpm dlx create-vite@latest apps/web-a -- --template react
pnpm dlx create-vite@latest apps/web-b -- --template react

步骤 5:安装依赖(根目录!)

pnpm install

步骤 6:集成 Turborepo

pnpm add -w -D turbo

创建 turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".vite/**", "dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

package.json 添加脚本:

{
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel"
  }
}

步骤 7:创建共享组件库

mkdir -p packages/ui
cd packages/ui
pnpm init

packages/ui/package.json

{
  "name": "@my-monorepo/ui",
  "version": "1.0.0",
  "main": "index.jsx"
}

packages/ui/index.jsx

export const SharedButton = () => (
  <button style={{ padding: '8px 16px', background: '#4f46e5', color: 'white' }}>
    Shared Button!
  </button>
);

步骤 8:在应用中使用共享组件(关键!)

在根目录运行:

pnpm add @my-monorepo/ui@workspace:* --filter web-a

这会在 apps/web-a/package.json 中添加:

"dependencies": {
  "@my-monorepo/ui": "workspace:*"
}

然后在 apps/web-a/src/App.jsx 中:

import { SharedButton } from '@my-monorepo/ui';

function App() {
  return (
    <div>
      <h1>Web App A</h1>
      <SharedButton />
    </div>
  );
}

步骤 9:验证

pnpm dev --filter web-a    # 应显示共享按钮
pnpm build                 # 应生成 dist 目录

四、目录结构

my-web-monorepo/
├── apps/
│   ├── web-a/        ← 商品页
│   └── web-b/        ← 用户中心
├── packages/
│   └── ui/           ← 共享 UI 组件
├── pnpm-workspace.yaml
├── turbo.json
├── package.json
└── .gitignore

五、常用命令

命令说明
pnpm install安装所有依赖(根目录)
pnpm dev --filter web-a仅启动 web-a
pnpm build构建所有应用(带缓存)
pnpm add lodash --filter web-a给 web-a 加外部依赖
pnpm add @my-monorepo/ui@workspace:* --filter web-a声明内部依赖

✅ 总结

你现在拥有一个:

  • 代码复用零成本 的共享组件机制
  • 独立开发/部署 的多应用架构
  • 秒级增量构建 的高性能工程体系

这个结构非常适合:

  • 开源组件库 + 示例站点
  • SaaS 产品的多个前端模块
  • 个人作品集(多个小项目统一管理)

🌟 Monorepo 不是银弹,但对紧密协作的项目,它是效率倍增器。