前端多仓库管理:从混乱到有序的进化之路

前言

在前端项目的发展过程中,随着业务的增长和团队的扩张,代码仓库的管理方式也在不断演变。从早期的单体仓库,到后来的多仓库模式,再到如今流行的 Monorepo 架构,每一种管理方式都有其适用场景和优缺点。

对于很多前端团队来说,多仓库管理是一个绕不开的话题。当项目规模扩大,需要拆分多个独立仓库时,如何高效地管理这些仓库之间的依赖关系、共享代码、统一构建流程,成为了团队面临的核心挑战。

本文将带你深入了解前端多仓库管理的现状、挑战以及解决方案,帮助你在实际项目中做出正确的决策,让多仓库管理从混乱走向有序。

一、为什么需要多仓库管理?

在项目初期,大多数团队都会采用单体仓库(Single Repo)的方式,将所有代码放在一个仓库中。这种方式简单直接,适合快速迭代和小型团队。但随着项目的发展,单体仓库会逐渐暴露出一些问题:

1.1 仓库体积过大

当项目代码量达到一定规模后,仓库体积会变得非常庞大,导致:

  • 克隆和拉取代码的时间变长
  • IDE 加载速度变慢,影响开发效率
  • Git 操作(如分支切换、合并)变得缓慢
  • 代码搜索和导航变得困难

1.2 团队协作冲突

在大型团队中,多个团队同时在一个仓库中开发,会导致:

  • 分支管理复杂,容易产生冲突
  • 代码审查变得困难,需要审查大量不相关的代码
  • 权限管理难以精细化,无法实现仓库级别的访问控制
  • 发布流程变得复杂,一个小改动可能影响整个项目

1.3 技术栈差异

随着业务的发展,不同模块可能需要使用不同的技术栈:

  • 主应用可能使用 Vue 3
  • 移动端可能使用 React Native
  • 工具库可能使用纯 TypeScript
  • 这些技术栈的差异会导致构建流程、依赖管理变得复杂

1.4 独立发布需求

某些模块可能需要独立发布和版本管理:

  • UI 组件库需要独立发布到 npm
  • SDK 需要支持多版本并行
  • 不同业务线可能需要独立的发布周期

正是由于这些原因,很多团队开始转向多仓库管理模式,将大型项目拆分为多个独立的仓库,每个仓库负责一个特定的模块或功能。

二、多仓库管理的常见挑战

虽然多仓库管理解决了单体仓库的一些问题,但它也带来了新的挑战。让我们来看看在实际操作中经常遇到的问题。

2.1 依赖版本不一致

在多仓库模式下,每个仓库都有自己的 package.json,这会导致:

问题示例

  • 仓库 A 使用 React 17
  • 仓库 B 使用 React 18
  • 仓库 C 使用 React 17.0.2

这种版本不一致会带来以下问题:

  • 组件在不同仓库中表现不一致
  • 升级依赖时需要逐个仓库更新,容易遗漏
  • 调试困难,因为不同仓库使用不同版本的依赖

解决方案 : 使用统一的依赖版本管理工具,如 npm-check-updatesrenovatedependabot,定期检查和更新所有仓库的依赖版本。

2.2 跨仓库代码共享困难

在多仓库模式下,代码共享变得非常困难:

问题场景

  • 仓库 A 中有一个通用的工具函数
  • 仓库 B 也需要使用这个函数
  • 仓库 C 同样需要

传统的解决方案有两种:

  1. 复制粘贴:将代码复制到每个仓库中,但这会导致维护困难,修改一处需要同步修改多处
  2. npm 包:将共享代码发布为 npm 包,但这会增加发布流程的复杂度,每次修改都需要重新发布

解决方案: 使用 Monorepo 架构,将所有仓库放在一个大仓库中,通过 workspace 机制实现代码共享。

2.3 重复构建耗时

在多仓库模式下,每个仓库都需要独立构建,这会导致:

  • CI/CD 流程变得漫长
  • 资源浪费,每个仓库都需要安装依赖和构建
  • 无法实现增量构建,即使代码没有变化也需要重新构建

解决方案: 使用支持增量构建和缓存的工具,如 Turborepo、NX 等。

2.4 版本协调困难

当多个仓库之间存在依赖关系时,版本协调变得非常复杂:

场景示例

  • 仓库 A 依赖仓库 B 的 v1.0.0
  • 仓库 B 更新到 v2.0.0,引入了破坏性变更
  • 仓库 A 如果不及时更新,会出现兼容性问题

解决方案: 建立清晰的版本依赖关系图,使用自动化工具跟踪和提醒版本更新。

2.5 开发环境配置复杂

每个仓库都需要配置自己的开发环境,这会导致:

  • 新成员加入时需要配置多个仓库的环境
  • 不同仓库的环境配置不一致,导致"在我电脑上可以运行"的问题
  • 环境配置文档难以维护和同步

解决方案: 使用容器化技术(如 Docker)统一开发环境,或者使用 Monorepo 共享配置。

三、Monorepo vs Multi-repo:如何选择?

在讨论多仓库管理时,我们不可避免地要面对 Monorepo 和 Multi-repo 的选择。这两种方式各有优缺点,适用于不同的场景。

3.1 什么是 Monorepo?

Monorepo(单一仓库)是一种将多个项目或模块放在同一个 Git 仓库中的管理方式。典型的 Monorepo 结构如下:

perl 复制代码
my-monorepo/
├── packages/
│   ├── app/           # 主应用
│   ├── ui/            # UI 组件库
│   ├── sdk/           # SDK
│   └── utils/         # 工具函数库
├── .gitignore
├── package.json
└── README.md

3.2 什么是 Multi-repo?

Multi-repo(多仓库)是一种将每个项目或模块放在独立 Git 仓库中的管理方式。典型的 Multi-repo 结构如下:

perl 复制代码
my-project/
├── app/               # 独立仓库
├── ui/                # 独立仓库
├── sdk/               # 独立仓库
└── utils/             # 独立仓库

3.3 优缺点对比

对比维度 Monorepo Multi-repo
代码共享 方便,直接引用 困难,需要发布 npm 包
依赖管理 统一,版本一致 分散,容易版本不一致
构建效率 支持增量构建,效率高 重复构建,效率低
团队协作 容易产生冲突 冲突较少
权限管理 难以精细化 容易精细化
发布流程 统一发布,复杂度高 独立发布,简单灵活
仓库体积 较大 较小
技术栈隔离 较难 容易

3.4 如何选择?

选择 Monorepo 还是 Multi-repo,取决于你的团队和项目情况:

选择 Monorepo 的场景

  • 团队规模较大,需要高效的代码共享
  • 多个项目之间有紧密的依赖关系
  • 需要统一的构建流程和工具链
  • 追求高效的 CI/CD 和增量构建

选择 Multi-repo 的场景

  • 每个项目相对独立,依赖关系较少
  • 需要精细化的权限管理
  • 不同项目使用不同的技术栈
  • 项目需要独立发布和版本管理

混合模式: 很多团队采用混合模式,将相关的项目放在一个 Monorepo 中,而将独立的项目放在单独的仓库中。例如:

bash 复制代码
frontend/              # Monorepo
├── packages/
│   ├── app/
│   ├── ui/
│   └── shared/

backend/               # 独立仓库
mobile/                # 独立仓库
docs/                  # 独立仓库

四、主流多仓库管理工具

随着前端工程化的发展,出现了许多优秀的多仓库管理工具。这些工具可以帮助我们解决多仓库管理中的各种问题。

4.1 pnpm Workspace

pnpm 是一个快速、节省磁盘空间的包管理器,它提供了强大的 workspace 功能,可以轻松管理多个包。

安装 pnpm

bash 复制代码
npm install -g pnpm

初始化 Workspace

bash 复制代码
mkdir my-monorepo && cd my-monorepo
pnpm init

配置 workspace : 在根目录创建 pnpm-workspace.yaml 文件:

yaml 复制代码
packages:
  - 'packages/*'
  - 'apps/*'

创建子包

bash 复制代码
mkdir -p packages/ui packages/utils apps/web
pnpm init -C packages/ui
pnpm init -C packages/utils
pnpm init -C apps/web

添加依赖

bash 复制代码
# 为所有包添加依赖
pnpm add lodash -w

# 为特定包添加依赖
pnpm add react -C apps/web

# 添加跨包依赖
pnpm add @my-monorepo/ui -C apps/web

运行脚本

bash 复制代码
# 运行所有包的 build 脚本
pnpm run build --filter '*'

# 运行特定包的脚本
pnpm run dev --filter @my-monorepo/web

# 并行运行多个脚本
pnpm run dev --filter '@my-monorepo/*' --parallel

优点

  • 安装速度快,节省磁盘空间
  • workspace 配置简单直观
  • 支持跨包依赖,无需发布即可引用
  • 内置脚本运行和过滤功能

缺点

  • 增量构建功能相对简单
  • 缺乏高级的任务编排能力

4.2 Turborepo

Turborepo 是 Vercel 推出的高性能 Monorepo 构建工具,专注于增量构建和缓存。

安装 Turborepo

bash 复制代码
# 使用官方脚手架初始化
npx create-turbo@latest

配置 turbo.json

json 复制代码
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["package.json"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "dev": {
      "cache": false
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    }
  }
}

运行任务

bash 复制代码
# 构建所有包
npx turbo run build

# 并行构建
npx turbo run build --parallel

# 只构建受影响的包
npx turbo run build --filter=@my-monorepo/web...

# 清理缓存
npx turbo prune --scope=@my-monorepo/web

优点

  • 增量构建速度极快
  • 智能缓存机制,避免重复构建
  • 支持远程缓存,团队共享构建结果
  • 清晰的任务依赖定义

缺点

  • 配置相对复杂
  • 生态相对较新,工具链不够丰富

4.3 NX

NX 是一个功能强大的 Monorepo 开发工具,提供了完整的开发体验。

安装 NX

bash 复制代码
# 使用官方脚手架初始化
npx create-nx-workspace@latest

创建应用和库

bash 复制代码
# 创建 React 应用
nx generate @nx/react:app web

# 创建共享库
nx generate @nx/react:lib ui-components

# 创建工具库
nx generate @nx/js:lib utils

运行任务

bash 复制代码
# 构建应用
nx build web

# 运行开发服务器
nx serve web

# 运行测试
nx test ui-components

# 只构建受影响的项目
nx affected:build

# 可视化依赖关系
nx graph

配置项目依赖

json 复制代码
{
  "name": "@my-monorepo/web",
  "dependencies": {
    "@my-monorepo/ui-components": "1.0.0",
    "@my-monorepo/utils": "1.0.0"
  }
}

优点

  • 功能全面,提供完整的开发工具链
  • 强大的代码生成能力
  • 丰富的插件生态,支持多种技术栈
  • 可视化依赖关系图

缺点

  • 学习曲线较陡
  • 配置复杂,灵活性较高

4.4 Lerna

Lerna 是一个老牌的 Monorepo 管理工具,专注于版本管理和发布。

安装 Lerna

bash 复制代码
npm install -g lerna

初始化 Lerna 项目

bash 复制代码
mkdir my-monorepo && cd my-monorepo
lerna init

配置 lerna.json

json 复制代码
{
  "$schema": "node_modules/lerna/schemas/lerna-schema.json",
  "version": "0.0.0",
  "npmClient": "pnpm",
  "useWorkspaces": true,
  "packages": ["packages/*"]
}

管理版本

bash 复制代码
# 查看所有包的版本
lerna ls

# 升级所有包的版本
lerna version

# 发布所有包
lerna publish

# 发布指定包
lerna publish --scope=@my-monorepo/ui

运行脚本

bash 复制代码
# 在所有包中运行脚本
lerna run build

# 在特定包中运行脚本
lerna run build --scope=@my-monorepo/web

# 并行运行脚本
lerna run dev --parallel

优点

  • 版本管理和发布功能强大
  • 支持独立版本和统一版本两种模式
  • 社区成熟,文档丰富

缺点

  • 构建性能相对较差
  • 功能相对单一,主要专注于版本管理

4.5 工具对比与选择

工具 核心优势 适用场景
pnpm Workspace 简单轻量,依赖管理高效 小型到中型项目,快速上手
Turborepo 增量构建,缓存高效 需要高性能构建的项目
NX 功能全面,工具链完整 大型企业级项目,复杂需求
Lerna 版本管理,发布流程 需要精细版本控制的项目

建议

  • 如果你的项目规模较小,追求简单高效,选择 pnpm Workspace
  • 如果你的项目需要高性能构建和缓存,选择 Turborepo
  • 如果你的项目是大型企业级应用,需要完整的工具链,选择 NX
  • 如果你的项目主要关注版本管理和发布,选择 Lerna

很多团队会组合使用这些工具,例如:pnpm Workspace + Turborepopnpm Workspace + Lerna

五、Multi-repo 管理策略与工具

虽然 Monorepo 是目前的主流趋势,但在很多场景下,Multi-repo(真正的多仓库)仍然是必要的选择。本节将介绍 Multi-repo 模式下的管理策略和工具。

5.1 Git Submodule

Git Submodule 是 Git 官方提供的多仓库管理方案,允许在一个仓库中引用另一个仓库作为子目录。

添加子模块

bash 复制代码
git submodule add https://github.com/user/repo.git packages/ui

初始化子模块

bash 复制代码
# 克隆包含子模块的仓库后
git submodule init
git submodule update

# 或者克隆时直接初始化
git clone --recursive https://github.com/user/main-repo.git

更新子模块

bash 复制代码
# 更新所有子模块到最新版本
git submodule update --remote

# 更新指定子模块
git submodule update --remote packages/ui

提交子模块变更

bash 复制代码
# 在子模块中提交变更
cd packages/ui
git add .
git commit -m "Update UI component"
git push

# 在主仓库中提交子模块引用更新
cd ..
git add packages/ui
git commit -m "Update UI submodule"
git push

优点

  • Git 原生支持,无需额外工具
  • 子模块可以独立版本管理
  • 适合引用第三方库或独立维护的模块

缺点

  • 操作复杂,学习成本高
  • 克隆和更新步骤繁琐
  • 容易忘记更新子模块
  • 子模块的分支管理复杂

5.2 Git Subtree

Git Subtree 是另一种多仓库管理方案,它将子仓库的代码直接合并到主仓库中,而不是作为独立的引用。

添加 subtree

bash 复制代码
git subtree add --prefix=packages/ui https://github.com/user/ui.git main

更新 subtree

bash 复制代码
git subtree pull --prefix=packages/ui https://github.com/user/ui.git main

推送变更到 subtree

bash 复制代码
git subtree push --prefix=packages/ui https://github.com/user/ui.git main

优点

  • 对开发者透明,无需特殊操作
  • 代码直接在主仓库中,方便查看和修改
  • 避免了 submodule 的复杂操作

缺点

  • 主仓库体积会增大
  • 分支管理和合并复杂
  • 推送变更到子仓库需要额外操作

5.3 Rush

Rush 是 Microsoft 开发的多仓库管理工具,专注于大型项目的依赖管理和发布流程。

安装 Rush

bash 复制代码
npm install -g @microsoft/rush

初始化 Rush 项目

bash 复制代码
mkdir my-rush-project && cd my-rush-project
rush init

配置 rush.json

json 复制代码
{
  "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
  "pnpmVersion": "9.0.0",
  "rushVersion": "6.0.0",
  "projects": [
    {
      "packageName": "@my-project/app",
      "projectFolder": "apps/app"
    },
    {
      "packageName": "@my-project/ui",
      "projectFolder": "packages/ui"
    },
    {
      "packageName": "@my-project/utils",
      "projectFolder": "packages/utils"
    }
  ]
}

安装依赖

bash 复制代码
rush install

构建项目

bash 复制代码
rush build

发布包

bash 复制代码
rush publish --apply

优点

  • 强大的依赖管理,支持版本策略
  • 支持增量构建和缓存
  • 提供统一的发布流程
  • 适合大型企业级项目

缺点

  • 配置复杂,学习曲线较陡
  • 生态相对较小

5.4 Changesets

Changesets 是一个用于管理版本和 CHANGELOG 的工具,特别适合多仓库项目。

安装 Changesets

bash 复制代码
pnpm install -D @changesets/cli

初始化 Changesets

bash 复制代码
npx changeset init

创建 changeset

bash 复制代码
npx changeset

执行版本更新

bash 复制代码
npx changeset version

发布包

bash 复制代码
npx changeset publish

配置示例

json 复制代码
{
  "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}

优点

  • 简单易用,专注于版本管理
  • 自动生成 CHANGELOG
  • 支持跨包版本关联
  • 适合需要独立版本管理的多包项目

缺点

  • 功能相对单一,主要关注版本管理
  • 需要与其他构建工具配合使用

5.5 跨仓库 CI/CD 编排

在 Multi-repo 模式下,跨仓库的 CI/CD 编排是一个重要的挑战。以下是一些常见的解决方案:

使用 GitHub Actions 编排

yaml 复制代码
name: Cross-repo Build
on:
  push:
    branches: [main]

jobs:
  build-ui:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          repository: user/ui
          path: ui
      - run: cd ui && npm install && npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: ui-build
          path: ui/dist

  build-app:
    runs-on: ubuntu-latest
    needs: build-ui
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: ui-build
          path: node_modules/@my-project/ui/dist
      - run: npm install && npm run build

使用 Jenkins Pipeline

groovy 复制代码
pipeline {
  agent any
  stages {
    stage('Build UI') {
      steps {
        git url: 'https://github.com/user/ui.git', branch: 'main'
        sh 'npm install && npm run build'
        stash includes: 'dist/**', name: 'ui-build'
      }
    }
    stage('Build App') {
      steps {
        git url: 'https://github.com/user/app.git', branch: 'main'
        unstash 'ui-build'
        sh 'npm install && npm run build'
      }
    }
  }
}

5.6 多仓库协调工具

除了上述工具外,还有一些专门用于多仓库协调的工具:

Repo Manager : 一个自定义的多仓库管理工具,支持批量操作、状态监控、任务编排等功能。参考设计文档 multi-repo-manager-design.md 了解详细设计。

Key Features

  • 批量克隆、拉取、推送代码
  • 统一依赖管理和版本同步
  • 实时状态监控和告警通知
  • 任务编排和自动化工作流

gh repo sync: GitHub CLI 的仓库同步命令,支持跨仓库同步分支和标签。

bash 复制代码
# 同步分支
gh repo sync user/repo --source user/source-repo --branch main

# 同步所有分支
gh repo sync user/repo --source user/source-repo --all

六、实战:搭建一个完整的多仓库项目

现在,让我们通过一个实际案例来演示如何搭建一个完整的多仓库项目。我们将使用 pnpm Workspace + Turborepo 的组合。

6.1 项目结构设计

首先,我们需要设计一个合理的项目结构。一个典型的多仓库项目结构如下:

perl 复制代码
my-monorepo/
├── apps/                  # 应用层
│   ├── web/               # Web 主应用
│   └── mobile/            # 移动端应用
├── packages/              # 包层
│   ├── ui/                # UI 组件库
│   ├── sdk/               # SDK
│   ├── utils/             # 工具函数库
│   └── hooks/             # 自定义 Hooks
├── .gitignore
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
└── README.md

设计原则

  • 清晰的层级:应用层和包层分离,便于管理
  • 单一职责:每个包只负责一个功能领域
  • 模块化:包之间尽量保持独立,减少耦合

6.2 初始化项目

步骤 1:创建项目目录

bash 复制代码
mkdir my-monorepo && cd my-monorepo

步骤 2:初始化 pnpm

bash 复制代码
pnpm init -y

步骤 3:创建 workspace 配置

bash 复制代码
cat > pnpm-workspace.yaml << EOF
packages:
  - 'apps/*'
  - 'packages/*'
EOF

步骤 4:初始化 Turborepo

bash 复制代码
npx turbo init -y

步骤 5:创建目录结构

bash 复制代码
mkdir -p apps/web apps/mobile
mkdir -p packages/ui packages/sdk packages/utils packages/hooks

6.3 配置基础工具

配置 tsconfig.json : 在根目录创建 tsconfig.json,共享 TypeScript 配置:

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@my-monorepo/ui": ["packages/ui/src/index.ts"],
      "@my-monorepo/sdk": ["packages/sdk/src/index.ts"],
      "@my-monorepo/utils": ["packages/utils/src/index.ts"],
      "@my-monorepo/hooks": ["packages/hooks/src/index.ts"]
    }
  },
  "include": ["apps/**/*", "packages/**/*"],
  "exclude": ["node_modules"]
}

配置 turbo.json

json 复制代码
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["package.json"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "dev": {
      "cache": false
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    }
  }
}

6.4 创建包和应用

创建 UI 组件库

bash 复制代码
cd packages/ui
pnpm init -y
pnpm add react react-dom @types/react @types/react-dom

创建 packages/ui/src/Button.tsx

tsx 复制代码
import React from 'react';

interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;
  variant?: 'primary' | 'secondary' | 'danger';
}

export const Button = ({ children, onClick, variant = 'primary' }: ButtonProps) => {
  const styles = {
    primary: {
      backgroundColor: '#3e95cd',
      color: 'white',
    },
    secondary: {
      backgroundColor: '#e0e0e0',
      color: '#333',
    },
    danger: {
      backgroundColor: '#e74c3c',
      color: 'white',
    },
  };

  return (
    <button
      onClick={onClick}
      style={{
        ...styles[variant],
        padding: '8px 16px',
        border: 'none',
        borderRadius: '4px',
        cursor: 'pointer',
        fontSize: '14px',
      }}
    >
      {children}
    </button>
  );
};

创建 packages/ui/src/index.ts

ts 复制代码
export { Button } from './Button';

创建工具函数库

bash 复制代码
cd ../utils
pnpm init -y

创建 packages/utils/src/format.ts

ts 复制代码
export const formatDate = (date: Date): string => {
  return date.toLocaleDateString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  });
};

export const formatNumber = (num: number): string => {
  return num.toLocaleString('zh-CN');
};

创建 packages/utils/src/index.ts

ts 复制代码
export { formatDate, formatNumber } from './format';

创建 Web 应用

bash 复制代码
cd ../../apps/web
pnpm init -y
pnpm add react react-dom @types/react @types/react-dom
pnpm add @my-monorepo/ui @my-monorepo/utils
pnpm add -D vite @vitejs/plugin-react typescript

创建 apps/web/vite.config.ts

ts 复制代码
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@my-monorepo/ui': '../../packages/ui/src',
      '@my-monorepo/utils': '../../packages/utils/src',
    },
  },
});

创建 apps/web/src/App.tsx

tsx 复制代码
import { Button } from '@my-monorepo/ui';
import { formatDate, formatNumber } from '@my-monorepo/utils';

function App() {
  const handleClick = () => {
    console.log('Button clicked!');
  };

  return (
    <div style={{ padding: '20px' }}>
      <h1>Welcome to My Monorepo</h1>
      <p>Today is: {formatDate(new Date())}</p>
      <p>Number: {formatNumber(1234567)}</p>
      <div style={{ marginTop: '20px' }}>
        <Button onClick={handleClick}>Primary Button</Button>
        <Button variant="secondary" style={{ marginLeft: '10px' }}>
          Secondary Button
        </Button>
        <Button variant="danger" style={{ marginLeft: '10px' }}>
          Danger Button
        </Button>
      </div>
    </div>
  );
}

export default App;

6.5 添加脚本命令

在根目录的 package.json 中添加脚本:

json 复制代码
{
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "clean": "turbo run clean && rm -rf node_modules",
    "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json}\""
  }
}

在各个包和应用的 package.json 中添加脚本:

packages/ui/package.json

json 复制代码
{
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "lint": "eslint ."
  }
}

packages/utils/package.json

json 复制代码
{
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "lint": "eslint ."
  }
}

apps/web/package.json

json 复制代码
{
  "scripts": {
    "build": "vite build",
    "dev": "vite",
    "lint": "eslint ."
  }
}

6.6 运行项目

构建所有包

bash 复制代码
pnpm build

启动开发服务器

bash 复制代码
pnpm dev

只构建受影响的包

bash 复制代码
pnpm build --filter=@my-monorepo/web...

七、多仓库管理的最佳实践

在实际项目中,要做好多仓库管理,除了选择合适的工具外,还需要遵循一些最佳实践。

7.1 保持清晰的目录结构

一个清晰的目录结构是多仓库管理的基础。建议按照以下原则组织:

perl 复制代码
my-monorepo/
├── apps/              # 应用层,存放可独立运行的应用
│   ├── web/           # Web 应用
│   ├── mobile/        # 移动端应用
│   └── admin/         # 管理后台
├── packages/          # 包层,存放可共享的代码
│   ├── ui/            # UI 组件库
│   ├── sdk/           # SDK
│   ├── utils/         # 工具函数库
│   ├── hooks/         # 自定义 Hooks
│   └── config/        # 共享配置
├── scripts/           # 脚本目录,存放自动化脚本
├── docs/              # 文档目录
└── tools/             # 工具目录,存放开发工具

7.2 统一依赖版本

在多仓库中,统一依赖版本非常重要。可以通过以下方式实现:

使用 pnpm 的 workspace 协议

bash 复制代码
pnpm add react -w

使用统一的版本配置文件 : 创建 .versionrc.jsonversions.json 文件,统一管理依赖版本:

json 复制代码
{
  "react": "^18.2.0",
  "typescript": "^5.0.0",
  "vite": "^5.0.0"
}

使用自动化工具 : 配置 renovatedependabot,定期检查和更新依赖版本。

7.3 建立明确的依赖关系

在多仓库中,包之间的依赖关系应该清晰明确。可以通过以下方式管理:

使用 TypeScript 的路径别名

json 复制代码
{
  "compilerOptions": {
    "paths": {
      "@my-monorepo/ui": ["packages/ui/src"],
      "@my-monorepo/utils": ["packages/utils/src"]
    }
  }
}

避免循环依赖: 循环依赖会导致构建失败和运行时错误,需要严格避免。可以使用以下工具检测:

  • madge:可视化依赖关系
  • tsc --listFiles:查看文件依赖

控制依赖深度: 尽量减少包之间的依赖深度,建议不超过 3 层。

7.4 实现增量构建

增量构建是提高多仓库构建效率的关键。可以通过以下方式实现:

使用 Turborepo 或 NX: 这些工具内置了强大的增量构建和缓存机制。

配置构建输出缓存 : 在 turbo.json 中配置输出目录:

json 复制代码
{
  "pipeline": {
    "build": {
      "outputs": ["dist/**"]
    }
  }
}

使用远程缓存: 配置 Turborepo 或 NX 的远程缓存,团队成员可以共享构建结果。

7.5 标准化代码规范

在多仓库中,保持代码规范的一致性非常重要。可以通过以下方式实现:

共享 ESLint 配置 : 创建 packages/eslint-config 包,共享 ESLint 配置。

共享 Prettier 配置 : 在根目录创建 .prettierrc 文件,统一格式化规则。

使用 Git Hooks : 配置 huskylint-staged,在提交代码前自动检查和格式化。

7.6 建立自动化工作流

自动化工作流可以大大提高团队的开发效率。建议配置以下自动化流程:

CI/CD 流程

  • 代码提交后自动运行 lint 和 test
  • 合并到主分支后自动构建和部署
  • 使用增量构建减少 CI 时间

版本管理

  • 使用 Conventional Commits 规范
  • 使用 standard-versionchangesets 自动生成版本号和 CHANGELOG

依赖更新

  • 使用 renovate 自动更新依赖
  • 使用 pnpm audit 定期检查安全漏洞

7.7 文档化和知识共享

在多仓库中,文档和知识共享尤为重要。建议:

编写 README 文件: 每个包和应用都应该有清晰的 README 文件,说明:

  • 包的功能和用途
  • 安装和使用方式
  • API 文档
  • 示例代码

创建架构文档: 编写架构文档,说明:

  • 项目结构和设计原则
  • 包之间的依赖关系
  • 构建和部署流程
  • 开发规范和最佳实践

定期分享和培训: 组织团队分享会,介绍项目结构和最佳实践,帮助新成员快速上手。

八、常见问题与解决方案

在多仓库管理的实践过程中,可能会遇到一些常见问题。下面我们来看看如何解决这些问题。

8.1 包之间的循环依赖

问题:包 A 依赖包 B,包 B 又依赖包 A,形成循环依赖。

解决方案

  1. 重构代码:将公共代码提取到独立的包中
  2. 使用类型导入:只导入类型,避免运行时依赖
  3. 使用事件机制:通过事件总线解耦包之间的依赖

示例

ts 复制代码
// 包 A
import type { Data } from '@my-monorepo/b';

export const processData = (data: Data): void => {
  // 处理数据
};

8.2 构建缓存失效

问题:Turborepo 或 NX 的缓存没有正确工作,导致每次都重新构建。

解决方案

  1. 检查输出目录配置 :确保 turbo.json 中正确配置了 outputs
  2. 检查全局依赖 :确保 globalDependencies 中包含了所有影响构建的文件
  3. 清理缓存 :运行 npx turbo prune 清理缓存后重新构建
  4. 检查环境变量:确保 CI 环境中正确配置了缓存

8.3 依赖版本冲突

问题:不同的包使用了不同版本的同一依赖,导致构建或运行时错误。

解决方案

  1. 使用 workspace 协议:在根目录安装依赖,确保版本一致
  2. 使用 resolutions :在 package.json 中强制指定依赖版本
  3. 使用 overrides :在 pnpm.overrides 中覆盖依赖版本

示例

json 复制代码
{
  "pnpm": {
    "overrides": {
      "react": "^18.2.0",
      "react-dom": "^18.2.0"
    }
  }
}

8.4 CI/CD 流程太慢

问题:多仓库的 CI/CD 流程运行时间过长,影响开发效率。

解决方案

  1. 使用增量构建:配置 Turborepo 或 NX 的增量构建
  2. 使用远程缓存:在 CI 中配置远程缓存
  3. 并行执行任务 :使用 --parallel 选项并行运行任务
  4. 只构建受影响的包 :使用 --filter 选项只构建受影响的包

8.5 新成员上手困难

问题:新成员加入后,需要花费大量时间了解项目结构和配置。

解决方案

  1. 编写详细的文档:包括项目结构、开发流程、工具使用等
  2. 创建入门指南:提供 step-by-step 的入门教程
  3. 配置统一的开发环境:使用 Docker 或脚本一键配置环境
  4. 组织结对编程:安排老成员与新成员结对,帮助快速上手

九、总结与展望

前端多仓库管理是一个复杂但重要的话题。随着项目规模的扩大和团队的成长,选择合适的仓库管理方式变得越来越关键。

在本文中,我们介绍了:

  • 多仓库管理的必要性和挑战
  • Monorepo 和 Multi-repo 的优缺点和选择依据
  • 主流 Monorepo 管理工具(pnpm Workspace、Turborepo、NX、Lerna)
  • Multi-repo 管理策略与工具(Git Submodule、Git Subtree、Rush、Changesets)
  • 跨仓库 CI/CD 编排方案
  • 实战案例:使用 pnpm Workspace + Turborepo 搭建项目
  • 多仓库管理的最佳实践
  • 常见问题与解决方案

未来,随着前端工程化的不断发展,我们可能会看到更多智能化的多仓库管理工具出现。这些工具将进一步简化仓库管理流程,提高开发效率。

无论选择哪种管理方式,核心原则都是:保持清晰的结构、统一的规范、高效的工具链和自动化的工作流。只有这样,才能让多仓库管理从混乱走向有序,让团队专注于业务开发,而不是繁琐的仓库管理工作。

希望本文能够帮助你更好地理解和实践前端多仓库管理。祝你在项目中取得成功!


📱 实用工具推荐

在日常工作和生活中,我们经常需要处理各种视频资源。如果你需要下载短视频平台上的视频,但又被烦人的水印困扰,那么这款小程序绝对值得一试!

三峋视频去水印

功能特点

  • 🎬 一键去水印:支持抖音、快手、小红书、B站等主流短视频平台,复制分享链接即可自动提取无水印视频
  • 🚀 极速下载:智能解析视频链接,秒级获取高清无水印视频
  • 💾 本地保存:下载的视频直接保存到手机相册,方便随时查看和当做素材使用或分享
  • 🆓 完全免费:所有功能永久免费使用,无解锁广告干扰,无隐藏收费

使用方法

  1. 在短视频平台找到喜欢的视频,点击分享按钮
  2. 复制视频的分享链接
  3. 打开「三峋视频去水印」小程序
  4. 粘贴链接,点击解析,即可获取无水印视频并下载

搜索方式

在微信小程序中搜索 「三峋视频去水印」,即可找到并使用这款实用工具!

无论是工作中需要提取视频素材,还是生活中想要保存喜欢的短视频,「三峋视频去水印」都能帮你轻松搞定,让去水印下载变得简单高效!

相关推荐
星栈1 小时前
写 Dioxus Demo 不难,难的是把它写成项目
前端·rust·前端框架
labixiong1 小时前
还原一个完整符合规范的 Promise(二)
前端·javascript
时光足迹1 小时前
腾讯云 TRTC UniApp SDK 从入门到上线
前端·vue.js·uni-app
时光足迹2 小时前
uni-app 里把加密视频嵌入页面播放?我分析了 4 种方案,只有 1 种接近完美
前端·vue.js·uni-app
To_OC2 小时前
万字解析《JS 语言精粹》之第五章:继承 5 大核心精髓(JS 原型核心)
前端·javascript·代码规范
时光足迹2 小时前
极光推送全攻略(上):被iOS证书折磨了三天,我写了一份前端也能看懂的避坑指南
前端·ios·uni-app
DyLatte2 小时前
AI 时代,最危险的不是被替代,而是努力不沉淀
前端·后端·程序员
mCell3 小时前
【锐评】桌面端技术营销:别拿跑分当工程判断
前端·rust·electron
柒和远方3 小时前
从一次工程审查看 AI 学习产品的边界兜底:RAG 资料链路一致性实战
前端·后端·架构