Turbo repo
新建项目
全局安装
bash1pnpm install turbo --global
创建 turbo 项目
bash1pnpm dlx create-turbo@latest
这时候会提示输入项目名以及选择哪个包管理器。

当创建会有如下的文件目录:

理解 packages/ui
首先打开/packages/ui/package.json,我们能够注意到它的 name 属性:
json1"name": "@repo/ui"
然后打开/apps/web/package.json中,它的 name 属性是这样的:
json1"name": "web"
然后查看它的依赖项:
json1 "dependencies": { 2 "@repo/ui": "workspace:*", 3 }
这就意味着我们的 web app 依赖了本地的ui包。
如果我们查看/apps/docs/package.json,会发现它的依赖项也是如此。
这说明 web 和 docs 都依赖了同一个包:来自本地的 @repo/ui包。
这种跨应用程序的共享代码模式在 monorepo 中非常常见,这意味着多个应用程序可以共享一个设计系统。
理解 imports 和 exports
查看/apps/docs/app/page.tsx文件。
typescript1import { Button } from '@repo/ui/button'
docs 和 web 都使用了 Next.js 构建。他们都直接从@repo/ui中引入了 Button 组件。那么这个 Button 是从哪里来的呢?
打开packages/ui/package.json查看一下:
typescript1 "exports": { 2 "./button": "./src/button.tsx", 3 "./card": "./src/card.tsx", 4 "./code": "./src/code.tsx" 5 },
你会发现都是从 packages/ui/src 中导出的。
尝试一下在/packages/ui/src/button.tsx继续导出一个ButtonProps类型
typescript1export interface ButtonProps { 2 children: ReactNode 3 className?: string 4 appName: string 5}
会发现也能够被page导入进来:
typescript1import { Button, ButtonProps } from '@repo/ui/button'
理解 typescript-config
现在我们还有两个工作区目录需要查看:typescript-config和eslint-config。
其中 typescript-config 的 name 属性是这样的:
json1"name": "@repo/typescript-config"
它有四个文件:
bash1packages/typescript-config/ 2├── base.json 3├── nextjs.json 4├── package.json 5└── react-library.json
packages/ui依赖了它:
json1{ 2 "devDependencies": { 3 "@repo/typescript-config": "workspace:*" 4 } 5}
在 packages/ui/tsconfig.json里,把 typescript-config 的react-library.json文件的配置extends过来了。
json1extends": "@repo/typescript-config/react-library.json"
这种模式允许 monoreo 在其所有工作区中共享一个 tsconfig.json,从而减少代码重复。
理解 eslint-config
它的 package.json是这样的:
json1 "name": "@repo/eslint-config", 2 "files": [ 3 "library.js", 4 "next.js", 5 "react-internal.js" 6 ]
这是针对不同的 package,使用不同的 eslint-config 的意思。
我们到apps/web/package.json能够看到引入了它:
"devDependencies": {
"@repo/eslint-config": "workspace:*"
}
再到apps/web/.eslintrc.js查看一下:
javascript1module.exports = { 2 extends: ['@repo/eslint-config/next.js'], 3}
这里引入了它的 next.js 的配置。
说明在 apps/web 这个项目中,eslintrc.js采用的是eslint-config/next.js里的配置。
小结
在工作区之间理解互相的依赖是非常重要的。现在的依赖关系是这样的:
- web - 依赖了 ui、typescript-config 和 eslint-config
- docs - 依赖了 ui、typescript-config 和 eslint-config
- ui - 依赖了 typescript-config 和 eslint-config
- typescript-config - 没有依赖,只有拆分成了不同用途的 config
- eslint-config - 没有依赖,只是拆分成了不同用途的 config
理解 turbo.json
现在我们已经理解了仓库的依赖关系了,那 turbo 还提供了什么功能呢?
Turborepo 会使运行任务更简单、更高效。
turbo.json 是这样的:
json1{ 2 "$schema": "https://turbo.build/schema.json", 3 "globalDependencies": ["**/.env.*local"], 4 "pipeline": { 5 "build": { 6 "dependsOn": ["^build"], 7 "outputs": [".next/**", "!.next/cache/**"] 8 }, 9 "lint": { 10 "dependsOn": ["^lint"] 11 }, 12 "dev": { 13 "cache": false, 14 "persistent": true 15 } 16 } 17}
我们在这里看到的是,我们已经向 turbo 注册了三个任务:lint、dev 和 build。在 turbo.json 中注册的每个任务都可以使用turbo-run<task>(简称turbo<task>)运行。
尝试执行lint程序:
bash1turbo run lint

在终端我们可以看到很多信息:
- 很多 lint 都被执行了:
@repo/ui:lint、web:lint、docs/lint等等。 - 有一个 lint 失败了。
- total 任务 3 个,其中 2 个成功了,一个失败了。
- 三个任务中有两个被缓存了。
- 总共耗时 3.813 秒
失败的任务是因为我们之前引入了一个 ButtonProps 但是没有使用:

删掉它之后重新跑一下:

全部成功且缓存!
每次运行的脚本都来自每个工作区的 package.json。每个工作区都可以选择指定自己的 lint 脚本:
apps/web/package.json
json1{ 2 "scripts": { 3 "lint": "next lint" 4 } 5}
apps/docs/package.json
json1{ 2 "scripts": { 3 "lint": "next lint" 4 } 5}
packages/ui/package.json
json1{ 2 "scripts": { 3 "lint": "eslint \"**/*.ts*\"" 4 } 5}
当我们运行 turbo lint 时,Turborepo 会查看每个工作区中的每个 lint 脚本并运行它。
使用缓存
如果我们重复执行 lint脚本,会发现除了第一次慢一点,后面的速度都很快。
比如第二次执行 lint 时耗时 329ms。
这是因为 Turbo-repo 意识到我们的代码并没有发生改变。
它保存了上次运行的日志,因此每次只是重新将日志展示出来。
随便修改一个文件的内容:
apps/web/app/page.tsx
diff1 - <Button appName="web" className={styles.button}> 2 - Click me! 3 - </Button> 4 5 + <Button appName="web" className={styles.button}> 6 + Hello Turbo repo 7 + </Button>
再执行一下:

@repo/ui:lint和docs/lint都有标识:cache hit。
web:lint标识为:cache miss,executing。
这说明没有修改过的代码会命中缓存,修改过代码的地方则会重新执行脚本。
使用 Turborepo 构建
现在尝试build脚本:
bash1turbo run build
您将看到与我们运行 lint 脚本时类似的输出。
并且在 docs 和 web 下都出现了.next 文件夹。

出现.next文件夹表示 turbo 已经完成了 task 并且它会将这些输出都保存在缓存里。
pipeline 上我们声明了 outputs意味着当 turbo 完成任务时,它会将您指定的输出保存在其缓存中。
Turborepo 缓存了我们以前构建的结果。当我们再次运行 build 命令时,它从缓存中恢复了整个.next/**文件夹。
使用 Turborepo 开发
现在尝试这个命令:
bash1turbo run dev

-
turbo 已经帮助我们执行了 docs 和 web 的开发环境了。
-
两个开发脚本同时运行,在端口 3000 和 3001 上启动 Next.js 应用程序。
-
docs:dev和web:dev:cache bypass,force executing 表示绕开了缓存,强制执行了脚本。
dev 命令的配置是这样的:
json1{ 2 "pipeline": { 3 "dev": { 4 "cache": false, 5 "persistent": true 6 } 7 } 8}
在 dev 中,我们指定了"cache": false。这意味着我们告诉 Turborepo 不要缓存 dev 脚本的结果。dev 运行一个持久的开发服务器并且不产生任何输出,所以没有什么可以缓存的。
另外,我们设定了"persistent": true,以让Turbo知道这是一个长期运行的开发服务器,这样 Turbo 可以确保没有其他任务依赖它。
仅运行一个 dev
默认情况下,turbo dev将同时在所有工作区上运行 dev。但有时,我们可能只想选择一个workspace 。
为了处理这个问题,我们可以在命令中添加一个--filter 标志。
bash1turbo run dev --filter=web
现在只有 web workspace 的 dev 脚本会被运行了。