参考官网:https://turbo.build/repo/docs
开始
安装全新的项目
pnpm dlx create-turbo@latest
测试应用包含:
- 两个可部署的应用
- 三个共享库
运行:
pnpm install
pnpm dev
会启动两个应用web(http://localhost:3000/)、docs(http://localhost:3001/)。
可以看到,两个应用都依赖了packages下的ui组件。
安装到已有项目中
按官方的说法,Turborepo可以安装到所有类型的应用中,不仅仅是multirepo,还有singlerepo。
安装turbo
官方建议是同时安装到全局和应用中
# Global install
pnpm add turbo --global
# Install in repository
pnpm add turbo --save-dev
新增一个turbo.json
文件
# 示例应用中的json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
编辑.gitignore
+ .turbo
初始化仓库
下面以monorepo为例。
目录结构:
最低要求
包管理
官方推荐apps目录下存放应用,packages目录下存放公共库或者工具类。
packages:
- "apps/*"
- "packages/*"
monorepo用pnpm比较好,所以只贴了pnpm的例子,如果要看yarn的,可以看Structuring a repository | Turborepo
使用此配置,apps或packages目录中包含package.json的每个目录都将被视为一个包。
Turborepo不支持嵌套,例如apps/或者packages/。
如果一定要嵌套,可以配置为packages/和packages/group/,然后不要创建packages/group/package.json文件
根目录package.json
示例:
{
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint"
},
"devDependencies": {
"turbo": "latest"
},
"packageManager": "pnpm@9.0.0"
}
packageManager不可少,否则报错。
根目录turbo.json
turbo.json用于配置turbo的行为。
lockfile
例如pnpm-lock.yaml
,这是复用缓存的关键。
依赖管理
依赖分为外部依赖和内部依赖。
{
"dependencies": {
"next": "latest", // External dependency
"@repo/ui": "workspace:*" // Internal dependency
}
}
创建一个内部包示例
在packages中新建math目录,作为公共仓库。
创建package.json
{
"name": "@repo/math",
"type": "module",
"scripts": {
"dev": "tsc --watch",
"build": "tsc"
},
"exports": {
"./add": {
"types": "./src/add.ts",
"default": "./dist/add.js",
},
"./subtract": {
"types": "./src/subtract.ts",
"default": "./dist/subtract.js",
},
},
"devDependencies": {
"@repo/typescript-config": "workspace:*",
"typescript": "latest"
}
}
exports: 为包定义多个入口点,以便可以在其他包中使用。import { add } from '@repo/math'
Turborepo会将@repo/math识别为@repo/typescript-config的依赖项,用于对任务进行排序。
添加tsconfig.json
,使用extends关键字继承公共配置:
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
在src中编写源码:
./packages/math/src/add.ts
export const add = (a: number, b: number) => a + b;
./packages/math/src/subtract.ts
export const subtract = (a: number, b: number) => a - b;
最后在应用中使用该库:
apps/web/package.json
"dependencies": {
+ "@repo/math": "workspace:*",
"next": "latest",
"react": "latest",
"react-dom": "latest"
},
apps/web/src/app/page.tsx
import { add } from '@repo/math/add';
function Page() {
return <div>{add(1, 2)}</div>;
}
export default Page;
在turbo.json
中添加编译缓存"dist/**"
,这样以后编译时就可以跳过math的编译:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
}
任务配置
定义tasks
tasks中每个key都是一个会被turbo run
执行的任务。Turborepo会在每个package.json
中查找同名脚本来执行。
例如一个最基础的task,不包含dependencies和outputs:
{
"tasks": {
"build": {} // Incorrect!
}
}
使用该配置,turbo将不会使用缓存,导致每次构建时间都很长。
指定tasks顺序
dependsOn
用于指定在另一个任务开始运行之前必须完成的任务。
假如你需要在应用编译前先执行公共仓库的构建,可以这样配置:
{
"tasks": {
"build": {
"dependsOn": ["^build"]
}
}
}
依赖^
的任务
^
告诉Turbo从依赖树的最底层开始构建。
假如你的应用依赖了一个仓库ui
且这个ui
有一个build
脚本,那么这个build
脚本会先执行。一旦这个ui
的build
任务执行完毕,应用的build
就会立即执行。
依赖同一个package中的任务
假如在当前应用中,你想在执行test
前先执行build
任务,test
需要定义dependsOn
为build
。(没有^
!)
{
"tasks": {
"test": {
"dependsOn": ["build"]
}
}
}
依赖特定包中的任务
有时你可能需要依赖特定包中的任务。
例如在所有lint
任务执行前先执行util
库中的build
任务:
{
"tasks": {
"lint": {
"dependsOn": ["utils#build"]
}
}
}
还可以更具体地说明相关任务,将其限制为某个包:
{
"tasks": {
"web#lint": {
"dependsOn": ["utils#build"]
}
}
}
使用此配置,web包中的lint任务只能在utils包中的构建任务完成后运行。
无依赖
在这种情况下,可以省略dependsOn键或提供一个空数组。
{
"tasks": {
"spell-check": {
"dependsOn": []
}
}
}
定义outputs
outputs告诉Turborepo在任务成功完成后应该缓存的文件和目录。如果不配,就不会使用缓存。
{
"tasks": {
"build": {
"outputs": ["dist/**"]
}
}
}
定义inputs
inputs用于指定要包含在任务哈希中以进行缓存的文件。默认情况下,Turborepo将包含Git跟踪的包中的所有文件。但是,您可以使用inputs键来更具体地确定哪些文件包含在散列中。
例如,在Markdown文件中查找拼写错误的任务可以这样定义:
{
"tasks": {
"spell-check": {
"inputs": ["**/*.md", "**/*.mdx"]
}
}
}
这样的话只有md文件的变更才会让turbo不使用缓存。
高级用例
有副作用的任务
有些任务无论如何都应该运行,比如在缓存构建之后运行部署脚本。可以使用"cache": false
:
{
"tasks": {
"deploy": {
"dependsOn": ["^build"],
"cache": false
},
"build": {
"outputs": ["dist/**"]
}
}
}