你维护着五六个项目,每个都单独开一个 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,用 pnpm 或 yarn workspaces 管理依赖,避免重复安装。
-
原子提交:一次 commit 修改多个项目,版本同步。
-
任务缓存:Turborepo 会记住每个任务的输入输出,第二次构建直接取缓存,秒完成。
二、准备工作:安装 pnpm 和 Turborepo
我们选择 pnpm 作为包管理器(比 npm/yarn 快,节省磁盘空间)。如果你没装 pnpm:
npm install -g pnpm
创建项目目录:
mkdir my-monorepo
cd my-monorepo
pnpm init
三、配置 pnpm workspace
在根目录创建 pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"
这样 apps/ 下的每个子目录是一个应用(比如 React 前端、Node 后端),packages/ 下的子目录是共享包(比如 UI 组件库、工具函数)。
四、安装 Turborepo
pnpm add -g turbo
# 或者在项目中安装
pnpm add -D turbo
创建 turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {},
"test": {}
}
}
pipeline 定义了任务依赖关系。^build 表示执行某个包的 build 之前,先构建它的依赖包。
五、创建共享组件库
mkdir -p packages/ui
cd packages/ui
pnpm init
packages/ui/package.json 中,给包起个名字(重要):
{
"name": "@myrepo/ui",
"version": "0.0.1",
"main": "./src/index.tsx",
"types": "./src/index.tsx",
"scripts": {
"build": "tsc"
}
}
安装 React 和 TypeScript 依赖(在根目录执行):
pnpm add -D react react-dom typescript @types/react -w
-w 表示安装在根 workspace。
写一个简单的 Button 组件:packages/ui/src/Button.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:
export { Button } from './Button';
配置 TypeScript:packages/ui/tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"module": "ESNext",
"target": "ES2020",
"declaration": true,
"outDir": "dist",
"strict": true
},
"include": ["src"]
}
六、创建 React 应用
我们用 Vite 创建一个 React 应用放在 apps/web:
cd apps
pnpm create vite web --template react-ts
cd web
修改 apps/web/package.json,添加对共享包的依赖:
"dependencies": {
"@myrepo/ui": "workspace:*",
...
}
workspace:* 表示使用当前 workspace 中的对应包。
在 apps/web/src/App.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 应用里产生输出:
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "build/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
然后在根 package.json 添加脚本:
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"lint": "turbo lint"
}
运行 pnpm dev,Turborepo 会同时启动两个应用的开发服务器(如果你还有 Node 后端的话)。第一次启动正常速度,第二次因为缓存,秒开。
八、共享配置与依赖提升
想在根目录统一管理 TypeScript、ESLint、Prettier 配置?在根目录创建 tsconfig.base.json,然后每个子项目的 tsconfig.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 一键构建?省下的时间,正好可以摸会儿鱼。