你的代码仓库变成“毛线团”了?Monorepo 用 Turborepo 拆成“乐高积木”

你维护着五六个项目,每个都单独开一个 Git 仓库。改一个公共组件,要挨个进每个项目,复制粘贴,提交,发布。一上午就没了。今天我们来学 Monorepo------用 Turborepo 把多个项目放进同一个仓库,共享代码、统一构建、一键发布。让你的"多仓库噩梦"变成"搭积木游戏"。

前言

Polyrepo(多仓库)刚开始很爽:每个项目独立,互不干扰。但公共代码一多,就成了复制粘贴地狱。你修了一个 bug,五个项目都要同步,漏一个线上就崩。

Monorepo(单仓库)不是把代码随便堆在一起,而是用工具(Turborepo、Nx、Lerna)把多个项目"有序地"放在同一个 Git 仓库里,让它们能共享依赖、共享配置、共享构建缓存。今天我们用 Turborepo(Vercel 出品,Next.js 同款团队)搭一个 Monorepo,里面有 React 应用、Node API、一个共享的 UI 组件库。全程实战,告别"复制粘贴工程师"。

一、Monorepo 解决了什么?

  • 代码共享 :公共组件放在 packages/shared,所有应用直接 import
  • 统一依赖 :根目录一个 package.json,用 pnpmyarn workspaces 管理依赖,避免重复安装。
  • 原子提交:一次 commit 修改多个项目,版本同步。
  • 任务缓存:Turborepo 会记住每个任务的输入输出,第二次构建直接取缓存,秒完成。

二、准备工作:安装 pnpm 和 Turborepo

我们选择 pnpm 作为包管理器(比 npm/yarn 快,节省磁盘空间)。如果你没装 pnpm:

bash 复制代码
npm install -g pnpm

创建项目目录:

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

三、配置 pnpm workspace

在根目录创建 pnpm-workspace.yaml

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

这样 apps/ 下的每个子目录是一个应用(比如 React 前端、Node 后端),packages/ 下的子目录是共享包(比如 UI 组件库、工具函数)。

四、安装 Turborepo

bash 复制代码
pnpm add -g turbo
# 或者在项目中安装
pnpm add -D turbo

创建 turbo.json

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

pipeline 定义了任务依赖关系。^build 表示执行某个包的 build 之前,先构建它的依赖包。

五、创建共享组件库

bash 复制代码
mkdir -p packages/ui
cd packages/ui
pnpm init

packages/ui/package.json 中,给包起个名字(重要):

json 复制代码
{
  "name": "@myrepo/ui",
  "version": "0.0.1",
  "main": "./src/index.tsx",
  "types": "./src/index.tsx",
  "scripts": {
    "build": "tsc"
  }
}

安装 React 和 TypeScript 依赖(在根目录执行):

bash 复制代码
pnpm add -D react react-dom typescript @types/react -w

-w 表示安装在根 workspace。

写一个简单的 Button 组件:packages/ui/src/Button.tsx

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

export const Button: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  return <button style={{ padding: '8px 16px', background: 'blue', color: 'white' }}>{children}</button>;
};

packages/ui/src/index.tsx

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

配置 TypeScript:packages/ui/tsconfig.json

json 复制代码
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "module": "ESNext",
    "target": "ES2020",
    "declaration": true,
    "outDir": "dist",
    "strict": true
  },
  "include": ["src"]
}

六、创建 React 应用

我们用 Vite 创建一个 React 应用放在 apps/web

bash 复制代码
cd apps
pnpm create vite web --template react-ts
cd web

修改 apps/web/package.json,添加对共享包的依赖:

json 复制代码
"dependencies": {
  "@myrepo/ui": "workspace:*",
  ...
}

workspace:* 表示使用当前 workspace 中的对应包。

apps/web/src/App.tsx 中引入共享按钮:

tsx 复制代码
import { Button } from '@myrepo/ui';

function App() {
  return (
    <div>
      <h1>Monorepo Demo</h1>
      <Button>来自共享组件库的按钮</Button>
    </div>
  );
}
export default App;

现在在根目录运行 pnpm install,它会自动链接本地包。

七、配置 Turborepo 任务

修改根 turbo.json,让 build 任务在 React 应用里产生输出:

json 复制代码
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", "build/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

然后在根 package.json 添加脚本:

json 复制代码
"scripts": {
  "dev": "turbo dev",
  "build": "turbo build",
  "lint": "turbo lint"
}

运行 pnpm dev,Turborepo 会同时启动两个应用的开发服务器(如果你还有 Node 后端的话)。第一次启动正常速度,第二次因为缓存,秒开。

八、共享配置与依赖提升

想在根目录统一管理 TypeScript、ESLint、Prettier 配置?在根目录创建 tsconfig.base.json,然后每个子项目的 tsconfig.json 继承它:

json 复制代码
// apps/web/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist"
  }
}

ESLint 同理,根目录装 eslint,每个子项目通过根配置运行。

九、生产构建与部署

运行 pnpm build,Turborepo 会按照依赖顺序构建:先构建 @myrepo/ui,再构建 apps/web。并且第二次构建时会复用缓存,毫秒级完成。

构建产物可以分别部署:apps/web/dist 部署到 Vercel/Netlify,Node 应用部署到服务器。因为它们在一个仓库里,但部署是独立的。

十、总结:Monorepo 不是银弹,但能救你于复制粘贴

  • 适合场景:多个项目共享代码、团队规模中等、希望统一 CI/CD。
  • 不适合 :项目之间几乎没有依赖、团队权限隔离要求极高(可加 CODEOWNERS 缓解)。
  • 工具选择:Turborepo 速度快、配置简单;Nx 功能更强(但复杂);Lerna 已过时(现在用 Nx 或 Turborepo)。

下次你又在不同项目间同步代码时,想一想:能不能把它们放进同一个 Monorepo,用 Turborepo 一键构建?省下的时间,正好可以摸会儿鱼。

相关推荐
better_liang1 天前
每日Java面试场景题知识点之-消息队列MQ核心场景与实战
java·面试·kafka·消息队列·rabbitmq·rocketmq·mq
英俊潇洒美少年1 天前
Vue 生产环境打包:SourceMap、压缩、混淆、加密全解 + 最佳实践
前端·javascript·vue.js
小江的记录本1 天前
【JVM虚拟机】垃圾回收GC:四种引用类型:强引用、软引用、弱引用、虚引用(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·后端·python·spring·面试
巴博尔1 天前
UNIAPP中NVUE页面 动画
android·前端·javascript·ios·uni-app
better_liang1 天前
每日Java面试场景题知识点之-SpringBoot启动流程
java·面试·springboot·源码解析·启动流程
Raink老师1 天前
【AI面试临阵磨枪-69】如何设计一个支持百万级工具的 Agent 系统?如何快速路由与选择工具?
人工智能·面试·职场和发展
猫头虎-前端技术1 天前
JS 作用域与闭包:从变量提升到闭包陷阱的超详细解析
开发语言·javascript·云计算·bootstrap·ecmascript·openstack·perl
Raink老师1 天前
【AI面试临阵磨枪-77】音视频 + AI:实时字幕、翻译、降噪、虚拟人、多模态对话
人工智能·面试·音视频
她说人狗殊途1 天前
基于 vue-cli 创建
前端·javascript·vue.js
AZaLEan__1 天前
前端移动端适配与 Bootstrap
前端·bootstrap·html