Monorepo 迁移血泪史:从 Multi-Repo 到 Turborepo,这 3 个坑我帮你踩完了

问题场景

团队项目越来越多,公共组件库、工具函数库、业务项目分散在 N 个 Git 仓库。每改一个公共包,流程是这样的:

  1. 改 A 库代码 → commit → push → npm publish
  2. 切到 B 项目 → 升级依赖 → 发现不兼容
  3. 回 A 修 → 再发版 → 再切回来 → 循环 N 次

同事直呼:"改一行代码,切五个仓库,发三个版,我的生命在流逝。"

你决定迁移到 Monorepo,但网上教程看着简单,落地上线时全是暗坑。


原因分析 & 方案选型

多仓库的核心痛点就三个:

痛点 表现
修改成本高 跨仓库改代码需要 N 次发版
版本碎片 不同项目依赖的公共包版本不一致
复现困难 issue 复现需要在多个仓库间来回跳

Monorepo 工具选型:Turborepo(Vercel 出品)vs Nx vs Lerna。

对比项 Turborepo Nx Lerna
学习曲线 ⭐ 低 ⭐⭐⭐ 高 ⭐ 低但功能弱
缓存能力 内置+远程缓存 内置+远程缓存
并行执行
任务编排 零配置 需配置 手动
社区生态 快速增长 成熟 逐渐边缘化

选 Turborepo,理由:Vite + pnpm + Turborepo 三件套,零配置任务编排、增量缓存、并行构建,小团队两周就能上手。


解决方案 & 实操步骤

Step 1: 目录结构设计(最容易被忽视)

perl 复制代码
my-monorepo/
├── apps/
│   ├── admin/          # 后台管理
│   ├── web/            # 前台 H5
│   └── docs/           # 文档站
├── packages/
│   ├── ui/             # 公共组件库
│   ├── utils/          # 工具函数
│   └── config/         # ESLint/TS 共享配置
├── pnpm-workspace.yaml
├── turbo.json
├── package.json
└── .npmrc

⚠️ 坑 1:packages 里面别放业务代码

有人把业务项目也丢在 packages 里,结果每次改业务代码都触发公共包的重建缓存失效。正确做法:apps 放业务项目,packages 放公共库,两者职责分离。

Step 2: pnpm workspace 配置

yaml 复制代码
# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"

根目录 .npmrc

ini 复制代码
shamefully-hoist=true
strict-peer-dependencies=false

⚠️ 坑 2:shamefully-hoist 不配,tsconfig paths 全崩

默认 pnpm 严格隔离依赖,Vite + TypeScript 路径别名会找不到 node_modules。加上 shamefully-hoist=true 将依赖提升到根 node_modules,否则每个子包都要单独配 tsconfig paths。

Step 3: Turborepo 任务编排

json 复制代码
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "dev": {
      "cache": false,
      "persistent": true
    },
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "test": {
      "dependsOn": ["build"]
    },
    "type-check": {
      "dependsOn": ["^type-check"]
    }
  }
}
jsonc 复制代码
// package.json (根目录)
{
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build",
    "lint": "turbo lint",
    "test": "turbo test"
  }
}

关键理解:dependsOn 中的 ^ 前缀

  • "^build" 表示:先构建该包的所有依赖,再构建它自己
  • 没有 ^:等所有前置任务的全部子包执行完
  • 不写 dependsOn:所有包并行执行

Turborepo 会自动拓扑排序:先构建 utils → 再构建依赖 utilsui → 最后构建依赖 uiweb

Step 4: 跨包引用

jsonc 复制代码
// apps/web/package.json
{
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/utils": "workspace:*"
  }
}
tsx 复制代码
// apps/web/src/App.tsx
import { Button } from "@repo/ui";  // 直接引用,无需发版
import { formatDate } from "@repo/utils";

⚠️ 坑 3:workspace: 和 ^1.0.0 的差别*

workspace:* 在本地开发时直接链接到本地源码,publish 时会自动替换为实际版本号。 如果写成 "@repo/ui": "^1.0.0",pnpm 会去 registry 找包,不走工作空间。务必写 workspace:*

Step 5: 缓存配置提速

Turborepo 默认有本地缓存,第二次构建相同输入直接秒出:

bash 复制代码
# 第一次:正常构建,耗时 45s
turbo build

# 第二次(代码没变):瞬间完成,耗时 0.2s
turbo build

# 强制跳过缓存:排查问题时用
turbo build --force

远程缓存(配合 Vercel Remote Caching 或自建 S3):

bash 复制代码
turbo login
turbo link
# 配置后 CI 和本地共享缓存,CI 构建从 10min → 45s

要点总结

序号 关键要点
1 apps 放业务,packages 放公共库,职责分离防止缓存污染
2 pnpm workspace 必须配 .npmrc + shamefully-hoist=true
3 跨包依赖用 workspace:*,不要写版本号
4 Turborepo 的 dependsOn 中的 ^ = 先构建依赖,理解它就能玩转任务编排
5 缓存是核心竞争力,配好远程缓存后 CI 速度飞升 10x

迁移完成的第一天,改一行 @repo/utils 的代码,所有项目自动生效的那一刻------同事们看着终端输出的 0.1s,露出了满意的微笑。

相关推荐
AI 编程助手GPT13 小时前
VS Code 1.133 实战:Claude 可免 GitHub 登录,HTML 保存后自动刷新
前端·html·github
灯澜忆梦13 小时前
【基于GO的Web开发3】gin框架_HTML渲染
前端·golang·gin
小徐_233320 小时前
wot-ui-cli 1.1.0 发布:支持 OpenCode、Antigravity,wot-ui 图标迁移不再靠猜
前端
__zRainy__20 小时前
Node.js Web 框架选型指南:从 Express 到 Hono 的全景对比
前端·node.js·express·koa·nestjs·egg·fastify
西瓜有点饿21 小时前
PostCSS 和 UnoCSS 的作用和区别
前端·postcss
伟大的兔神21 小时前
我做了一个本地优先的 AI 图片工作台:Loomora v1.0.0 正式发布
前端·javascript·vue.js
90后的晨仔1 天前
uni-app项目 Vue3 状态管理 Pinia 完全指南:从概念到实战的深度解析
前端
90后的晨仔1 天前
uni-app 在 iOS 平台跳转页面时移除底部安全区域的完整技术指南
前端
用户938515635071 天前
React + JWT 登录鉴权底层原理与工程化实践
前端·react.js
zww89491111 天前
家政派单系统开发实战:架构设计与派单算法指南
前端·系统架构