随着前端项目日益复杂,团队规模不断扩大,我们正面临一个棘手的问题:项目间的代码复用、依赖管理和构建流程变得越来越混乱。传统的“一个项目一个仓库”(Polyrepo) 模式,导致了严重的“轮子”重复制造、版本不一致和协作效率低下。
是时候引入一种更先进的组织方式了:Monorepo。它并不是一个新概念,Google、Facebook 等巨头早已大规模采用。而现在,借助 pnpm
和 Turborepo
这对黄金搭档,我们普通开发者也能轻松搭建起一套高效、现代化的 Monorepo 工程体系。这篇文章将手把手带你从零开始,构建一个能解决实际工作痛点的 Monorepo 项目。
简单来说,Monorepo 就是将多个相互关联的项目、库或应用,统一放在一个代码仓库中进行管理。它的核心优势在于:
ui
组件库、utils
工具函数库,供仓库内所有应用消费,避免复制粘贴。node_modules
和 lockfile
。依赖版本高度统一,彻底告别“我这里是好的”这类环境问题。虽然 npm
、yarn
也能实现 Monorepo (workspace),但 pnpm
有其天生的优势:
node_modules
。这不仅极大地节省了磁盘空间,更重要的是,它从根本上解决了“幽灵依赖”问题。在 pnpm 的工作区里,一个项目如果没有在 package.json
中明确声明某个依赖,就绝对无法 import
它,保证了依赖关系的纯粹性。而 Turborepo
则是 Monorepo 的“涡轮增压引擎”:
pnpm
解决了“依赖”层面的问题,Turborepo
解决了“构建与任务”层面的问题。它们结合在一起,构成了当前社区公认的最佳 Monorepo 实践。
让我们来构建一个实际的例子。这个 Monorepo 将包含:
apps/web
: 一个 Next.js 网站应用。apps/docs
: 一个 VitePress 文档站。packages/ui
: 一个共享的 React 组件库。packages/utils
: 一个共享的工具函数库。创建项目并初始化 pnpm-workspace.yaml
文件,这个文件是 pnpm 用来识别工作区范围的。
mkdir my-turborepo && cd my-turborepo
pnpm init
echo "packages:\n - 'apps/*'\n - 'packages/*'" > pnpm-workspace.yaml
创建目录结构:
mkdir -p apps packages
将 turbo
安装到项目的根 devDependencies
中。
pnpm add turbo -D -w # -w 标志表示安装到工作区根目录
ui
和 utils
)packages/ui
: 一个共享的 React UI 库。
mkdir -p packages/ui
cd packages/ui
pnpm init
# 安装 react
pnpm add react
# 创建一个按钮组件
mkdir src
echo "export const Button = () => ;" > src/Button.jsx
# 创建入口文件
echo "export * from './src/Button';" > index.jsx
cd ../..
修改 packages/ui/package.json
,添加 main
和 exports
字段:
{
"name": "@repo/ui",
"version": "0.0.0",
"main": "./index.jsx",
"exports": {
".": "./index.jsx"
}
}
packages/utils
: 一个共享的工具函数库。这个库甚至可以不依赖任何框架。
mkdir -p packages/utils
cd packages/utils
pnpm init
echo "export const add = (a, b) => a + b;" > index.js
cd ../..
修改 packages/utils/package.json
:
{
"name": "@repo/utils",
"version": "0.0.0",
"main": "./index.js"
}
web
)我们创建一个 Next.js 应用,并让它消费共享的包。
cd apps
# 使用官方脚手架创建 Next.js 应用
pnpx create-next-app@latest web --use-pnpm
cd web
# 关键:将共享包作为依赖安装
pnpm add @repo/ui @repo/utils
现在,你可以在 apps/web/src/app/page.js
中使用这些共享组件了:
import { Button } from "@repo/ui";
import { add } from "@repo/utils";
export default function Home() {
return (
Web App
2 + 3 = {add(2, 3)}
);
}
这是最后也是最关键的一步。在项目根目录创建 turbo.json
文件。
turbo.json
:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
配置解析:
pipeline
: 定义了可以在 Monorepo 中运行的任务(scripts)。build
: 定义了 build
任务。
dependsOn: ["^build"]
: 这是核心。^
符号表示“拓扑依赖”。这意味着在构建一个应用(如 web
)之前,Turborepo 会自动先构建 web
所依赖的所有包(如 ui
和 utils
)的 build
任务。outputs
: 声明了这个任务会产生哪些输出目录。Turborepo 会缓存这些目录,如果下次构建时代码没有变化,就直接使用缓存。dev
: 定义了 dev
任务。
cache: false
: 开发服务器不应该被缓存。persistent: true
: 告诉 Turborepo 这是一个长期运行的进程。现在,在根目录的 package.json
中添加 scripts:
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint"
}
要同时启动所有应用的开发服务器,只需在根目录运行:
pnpm dev
Turborepo 会智能地并发运行所有 dev
脚本,并用漂亮的 UI 展示给你。
我们刚刚搭建的,就是一个现代化的、功能完备的 Monorepo 项目。它看似复杂,但其背后的逻辑和带来的好处是巨大的。
核心要点就是:
pnpm-workspace.yaml
定义你的工作区,让 pnpm 管理依赖。turbo.json
定义任务依赖和缓存策略,实现高效的构建和开发流程。告别散落在各处的项目和混乱的依赖吧。拥抱 pnpm + Turborepo,你将获得前所未有的工程化掌控力,让你的项目管理变得清晰、高效和愉快。