某人

此前素未谋面、此后遥遥无期

0%

turborepo

Turborepo 高性能构建系统

Turborepo 是一个用于 JavaScriptTypeScript monorepos 的高性能构建系统。Turborepo 重新设计了 Facebook 和谷歌使用的构建系统技术,以消除维护负担和开销。

特性:

  • 增量构建: 构建一次已经足够痛苦了,Turborepo会记住您构建的内容,并跳过已经计算过的内容。
  • 内容hashTurborepo查看您的文件内容,而不是时间戳来找出需要构建的内容。
  • 远程缓存:与您的团队和CI/CD共享远程构建缓存以获得更快的构建。
  • 并行执行:在不浪费空闲cpu的情况下,以最大的并行度使用每个核执行构建。
  • 零运行时开销:Turborepo不会干扰您的运行时代码或触及您的源代码映射。
  • 删减子集:通过生成一个只需要构建特定目标的 monorepo 的子集来加快 PaaS 的部署速度
  • 任务管道:定义任务之间的关系,然后让Turborepo优化要构建的内容和时间。
  • 基于约定的配置:通过约定来降低配置的复杂度,只需要几行简单的 JSON 就能完成配置
  • 浏览器中的配置文件:生成构建配置文件并在 ChromeEdge 中导入,以了解哪些任务花费的时间最长

缓存任务

每个JavaScriptTypeScript代码库都需要运行package.json脚本,如build, testlint。在Turborepo中,我们称之为任务。

Turborepo可以缓存任务的结果和日志-对于慢速任务可以极大地加速。

丢失缓存

假设你在Turborepo中使用turbo run build运行一个构建任务:

image

  1. Turborepo将计算任务的输入(默认是工作区文件夹中的所有非git忽略文件)并将它们转换为哈希值(例如78awdk123)。
  2. 检查本地文件系统缓存中以散列命名的文件夹(例如../node_modules/.cache/turbo/78awdk123)。
  3. 如果Turborepo没有为计算的散列找到任何匹配的工件,那么Turborepo将执行该任务。
  4. 任务结束后,Turborepo将所有输出(包括文件和日志)保存到哈希下的缓存中。

命中缓存

假设你在不改变任何输入的情况下再次运行任务:

  1. 散列将是相同的,因为输入没有改变(例如78awdk123)
  2. Turborepo将在其缓存中找到带有计算散列的文件夹(例如:/node_modules/.cache/turbo/78awdk123)
  3. Turborepo不运行任务,而是重播输出——将保存的日志打印到标准输出,并将保存的输出文件恢复到文件系统中各自的位置。

从缓存中恢复文件和日志几乎是瞬间发生的。这可以将构建时间从几分钟或几小时缩短到几秒或几毫秒。尽管具体的结果会根据代码库依赖关系图的形状和粒度而有所不同,但大多数团队发现,使用Turborepo的缓存,他们可以将每月的总体构建时间缩短约40-85%

配置缓存输出

使用管道,您可以在Turborepo中配置缓存约定。

要覆盖默认的缓存输出行为,将一个glob数组传递给管道。输出数组。任何满足任务glob模式的文件都将被视为工件。

如果你的任务不产生任何文件(例如使用jest进行单元测试),将输出设置为空数组(即[]),Turborepo将为你缓存日志。

当您运行turbo运行构建测试时,Turborepo将执行您的构建和测试脚本,并将其输出缓存到./node_modules/.cache/turbo中。

配置缓存输入

当工作空间中的任何文件发生更改时,就认为该工作空间已经更新。但是,对于某些任务,我们只希望在相关文件发生更改时重新运行该任务。指定输入可以让我们定义哪些文件与特定任务相关。例如,下面的测试配置声明,测试任务只需要
src/test/子目录中的.tsx.ts文件自上次执行以来发生更改时才需要执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
// ... omitted for brevity

"test": {
// A workspace's `test` task depends on that workspace's
// own `build` task being completed first.
"dependsOn": ["build"],
"outputs": [],
// A workspace's `test` task should only be rerun when
// either a `.tsx` or `.ts` file has changed.
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
}
}
}

关闭高速缓存

1
2
3
4
turbo run dev --parallel --no-cache
# 注意`--no-cache`禁用缓存写,但不禁用缓存读。如果你想禁用缓存读取,使用`--force`标志。
# 在所有工作区中并行运行“dev”npm脚本
# 但不缓存输出
1
2
3
4
5
6
7
8
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"dev": {
"cache": false
}
}
}

基于文件更改缓存

对于某些任务,如果不相关的文件发生更改,您可能不希望缓存丢失。例如,更新README.md可能不需要为测试任务触发缓存缺失。您可以使用输入来限制turbo为特定任务考虑的文件集。

在这种情况下,只考虑与确定测试任务上的缓存命中相关的.ts.tsx文件:

1
2
3
4
5
6
7
8
9
10
11
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
// ...other tasks
"test": {
"outputs": [], // leave empty to only cache logs
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
}
}
}

基于环境变量修改缓存

你可以根据环境变量的值来控制turbo的缓存行为:

在管道定义中的env键中包含环境变量将影响每个任务或每个工作空间任务的缓存指纹。

任何在其名称中包含THASH的环境变量的值都会影响所有任务的缓存指纹。

强制覆盖缓存

相反,如果你想禁止读取缓存并强制turbo重新执行之前缓存的任务,添加--force标志:

1
2
3
# Run `build` npm script in all workspaces,
# ignoring cache hits.
turbo run build --force

日志

turbo不仅缓存任务的输出,它还记录终端输出(即结合stdoutstderr)到(<package>/.turbo/run-<command>.log)。当turbo遇到一个缓存的任务时,它会重新播放输出,就像它再次发生一样,但是是即时的,包的名称略微暗淡。

哈希

您可能想知道turbo如何决定给定任务的缓存命中和未命中。

  1. 首先,turbo构造了代码库当前全局状态的哈希值
  2. 然后它添加了更多与给定工作空间任务相关的因素

一旦turbo在执行过程中遇到给定工作区的任务,它就会检查缓存(本地和远程)以查找匹配的散列。如果匹配,则跳过执行该任务,将缓存的输出移动或下载到合适的位置,并立即重放先前记录的日志。如果缓存(本地或远程)中没有任何与计算的哈希匹配的内容,turbo将在本地执行任务,然后使用哈希作为索引缓存指定的输出。

给定任务的哈希值在执行时作为环境变量TURBO_HASH注入。这个值在打印输出或标记Dockerfile等方面很有用。

远程缓存

Turborepo的任务缓存可以节省大量时间,因为它不会重复同样的工作。

但是有一个问题-缓存是本地的。当您使用CI时,这可能会导致大量重复工作

因为默认情况下Turborepo只缓存到本地文件系统,所以相同的任务(Turbo运行构建)必须在每台机器上(由您、您的队友、您的CI、您的PaaS等)重新执行,即使所有的任务输入都是相同的–这浪费了时间和资源。

远程缓存是Turborepo的一个强大特性,但是能力越大责任越大。首先要确保缓存正确,并仔细检查环境变量的处理情况。还请记住,Turborepo将日志视为工件,因此要注意打印到控制台的内容。

一个单一的共享缓存

通过与Vercel等提供商合作,Turborepo可以安全地与远程缓存(存储任务结果的云服务器)通信。

这可以通过防止整个组织中的重复工作来节省大量的时间。

Vercel

本地开发,如果您想将本地turborepo链接到远程缓存,请先使用您的Vercel帐户验证turborepo CLI:

1
npx turbo login

启用后,对当前正在缓存的工作空间进行一些更改,并使用turbo run对其运行任务。您的缓存工件现在将存储在本地和远程缓存中。

为了验证,删除您的本地Turborepo缓存:

然后再次运行相同的构建。如果一切正常,turbo不应该在本地执行任务,而是从远程缓存下载日志和工件,并将它们重放给您。

自定义远程缓存

您可以自行托管自己的远程缓存或使用其他远程缓存服务提供商,只要他们符合Turborepo的远程缓存服务器API

您可以通过指定--api--token标志来设置远程缓存域,其中--api是主机名,--token是承载令牌。

1
turbo run build --api="https://my-server.example.com" --token="xxxxxxxxxxxxxxxxx"

您可以在这里看到所需的端点/请求在一个新选项卡中打开

拓扑

拓扑排序通常用来“排序”具有依赖关系的任务, 如果 A 依赖于 BB 依赖于 C,则拓扑顺序为 C、B、A

配置文件turbo.json

monorepo的根目录中,创建一个名为turbo.json的空文件。这将保持Turborepo的配置。

要定义monorepo的任务依赖关系图,请使用turbo中的pipeline键。Turbo解释这个配置,以优化调度、执行和缓存每个包的输出。在工作空间中定义的Json脚本。

./turbo.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
// A package's `build` script depends on that package's
// dependencies and devDependencies
// `build` tasks being completed first
// (the `^` symbol signifies `upstream`).
"dependsOn": ["^build"],
// note: output globs are relative to each package's `package.json`
// (and not the monorepo root)
"outputs": [".next/**"]
},
"test": {
// A package's `test` script depends on that package's
// own `build` script being completed first.
"dependsOn": ["build"],
"outputs": [],
// A package's `test` script should only be rerun when
// either a `.tsx` or `.ts` file has changed in `src` or `test` folders.
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
},
"lint": {
// A package's `lint` script has no dependencies and
// can be run whenever. It also has no filesystem outputs.
"outputs": []
},
"deploy": {
// A package's `deploy` script depends on the `build`,
// `test`, and `lint` scripts of the same package
// being completed. It also has no filesystem outputs.
"dependsOn": ["build", "test", "lint"],
"outputs": []
}
}
}

全局依赖(globalDependencies)

用于隐式全局哈希依赖的文件glob列表。这些文件的内容将包含在全局哈希算法中,并影响所有任务的哈希。

这对于基于.env文件(不在Git中)或任何影响工作空间任务的根级文件(但在传统的依赖关系图中没有表示)破坏缓存非常有用(例如根tsconfig)。Json, jest.config.js, .eslintrc等))。

例子:

1
2
3
4
5
6
7
8
9
10
11
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
// ... omitted for brevity
},

"globalDependencies": [
".env", // contents will impact hashes of all tasks
"tsconfig.json" // contents will impact hashes of all tasks
]
}

全局环境(globalEnv)

用于隐式全局哈希依赖关系的环境变量列表。这些环境变量的内容将包含在全局哈希算法中,并影响所有任务的哈希值。

1
2
3
4
5
6
7
8
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
// ... omitted for brevity
},

"globalEnv": ["GITHUB_TOKEN"] // value will impact the hashes of all tasks
}

管道(pipeline)

表示项目的任务依赖关系图的对象。Turbo解释这些约定,以正确地调度、执行和缓存项目中任务的输出。

管道对象中的每个键都是turbo运行可以执行的任务的名称。如果turbo找到一个带有包的工作空间。对象,它将在执行期间将管道任务配置应用到该NPM脚本。这允许您使用管道在整个Turborepo中设置约定。

依赖于(DependsOn)

dependsOn中的一个项目前面加上^,告诉turbo这个管道任务依赖于工作空间的拓扑依赖关系,首先用^前缀完成任务(例如:“一个工作空间的构建任务应该只在它的所有依赖项和devDependencies完成了它们自己的构建命令后运行”)。

dependsOn中没有^前缀的项表示工作空间级别任务之间的关系(例如:“工作区的testlint命令依赖于build首先完成”)。

dependsOn中的项前面加上$可以告诉turbo这个管道任务依赖于该环境变量的值。

不建议在dependsOn配置中使用$声明环境变量。请使用env键。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
// "A workspace's `build` command depends on its dependencies'
// or devDependencies' `build` command being completed first"
"dependsOn": ["^build"]
},
"test": {
// "A workspace's `test` command depends on its own `lint` and
// `build` commands first being completed"
"dependsOn": ["lint", "build"]
},
"deploy": {
// "A workspace's `deploy` command, depends on its own `build`
// and `test` commands first being completed"
"dependsOn": ["build", "test"]
},
// A workspace's `lint` command has no dependencies
"lint": {}
}
}
  • 空依赖: 如果一个任务的dependsOn[] 或者不声明这个属性,那么表明这个任务可以在任意时间被执行

env

类型:string []

任务所依赖的环境变量列表。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"env": ["SOMETHING_ELSE"], // value will impact the hashes of all build tasks
"outputs": ["dist/**", ".next/**"]
},
"web#build": {
"dependsOn": ["^build"],
"env": ["STRIPE_SECRET_KEY"], // value will impact hash of only web's build task
"outputs": [".next/**"]
}
},
"globalEnv": [
"GITHUB_TOKEN" // value will impact the hashes of all tasks
]
}

outputs

类型:string []

默认为["dist/**", "build/**"]。任务的可缓存文件系统输出的glob模式集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
// "Cache all files emitted to workspace's dist/** or .next
// directories by a `build` task"
"outputs": ["dist/**", ".next/**"],
"dependsOn": ["^build"]
},
"test": {
// "Don't cache any artifacts of `test` tasks (aside from
// logs)"
"outputs": [],
"dependsOn": ["build"]
},
"test:ci": {
// "Cache the coverage report of a `test:ci` command"
"outputs": ["coverage/**"],
"dependsOn": ["build"]
},
"dev": {
// Never cache anything (including logs) emitted by a
// `dev` task
"cache": false
}
}
}

Cache

默认为true。是否缓存任务输出。将缓存设置为false对于不希望缓存的守护进程或长时间运行的“监视”或开发模式任务非常有用。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"]
},
"test": {
"outputs": [],
"dependsOn": ["build"]
},
"dev": {
"cache": false
}
}
}

inputs

默认为[]。告诉turbo在确定工作空间是否为特定任务更改时要考虑的文件集。将此设置为一个glob列表将导致仅当匹配这些glob的文件发生更改时才重新运行任务。例如,如果您希望跳过运行测试,除非源文件发生了更改,那么这将很有帮助。

指定[]将导致在工作区中的任何文件更改时重新运行任务

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
// ... omitted for brevity

"test": {
// A workspace's `test` task depends on that workspace's
// own `build` task being completed first.
"dependsOn": ["build"],
"outputs": [""],
// A workspace's `test` task should only be rerun when
// either a `.tsx` or `.ts` file has changed.
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
}
}
}

outputMode

outputMode代表输出的模式类型是字符串

  • full:默认值。显示所有输出
  • hash-only:只显示任务的哈希值
  • new-only:只显示缓存未命中的输出
  • none:隐藏所有任务输出
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputMode": "new-only"
},
"test": {
"outputs": [],
"dependsOn": ["build"]
}
}
}

命令行

在安装turbo包(或克隆一个启动器)之后,您可以开始使用Turborepo的命令行界面(CLI) turbo在您的monorepo中做各种事情。

全局参数

1
2
3
4
5
turbo run build --color
# 为CI运行器启用turbo的颜色输出,这些运行器支持在日志输出中呈现颜色。

turbo run build --no-color
# 禁止在输出中使用颜色

任务参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
turbo run build --cache-dir="./my-cache"
# 默认为。/node_modules/.cache/turbo。指定本地文件系统缓存目录。如果更改默认值,请确保将此文件夹添加到.gitignore中

turbo run build --concurrency=50%
# 默认值为10。设置/限制任务执行的最大并发数。这必须是大于等于1的整数或百分比值,如50%,如果还传递了--parallel标志,则忽略此选项。

turbo run build --continue
# 默认为false。这个标志告诉turbo在出现错误(即任务的非零退出码)时是否继续执行。默认情况下,指定--parallel标志将自动设置--continue为true,除非显式设置为false。当--continue为true时,turbo将以执行期间遇到的最高退出码值退出。

turbo run build --cwd=./somewhere/else
# 设置命令的工作目录。

turbo run build --filter=my-pkg --filter=my-app
# 你可以通过给命令传递多个--filter标志来指定多个过滤器

turbo run build --filter=admin-*
# 构建所有以“admin-”开头的工作区,让turbo推断任务

turbo run test --filter=...my-lib
# 测试'my-lib'和所有依赖于'my-lib'的东西

turbo run test --filter=...^my-lib
# 测试所有依赖于'my-lib'的东西,而不是'my-lib'本身

turbo run build --filter=./apps/*
# 在"apps"目录下构建所有的工作空间

turbo run build --graph
# 该命令将生成当前任务图的svg、png、jpg、pdf、json、html或其他支持的输出格式(在新选项卡中打开)。输出文件格式默认为jpg,但可以通过指定文件名的扩展名来控制。

turbo run build --force
# 忽略现有的缓存工件并强制重新执行所有任务

turbo run build --global-deps=".env.*" --global-deps=".eslintrc" --global-deps="jest.config.js"
# 指定要散列的全局文件系统依赖项的`glob`。适用于`.env`和根目录下影响多个包/应用程序的文件。可以多次指定。

turbo run build --ignore="apps/**/*"
# 忽略影响作用域的文件或目录。在盖下使用glob模式

turbo run build --no-cache
# 默认的错误。不要缓存任务的结果。这对于监视命令(如next dev或react-scripts start)非常有用。

turbo run build --output-logs=full
# full默认值。显示所有输出;hash-only只显示任务的哈希值;new-only只显示缓存未命中的输出;none隐藏所有任务输出

turbo run test --only
# 默认的错误。限制执行只包括指定的任务。这与lerna和pnpm默认运行任务的方式非常相似。

turbo run lint --parallel --no-cache
# 默认的错误。跨工作空间并行运行命令并忽略依赖关系图。这对于实时重载开发非常有用。

turbo run build --remote-only
# 默认的错误。忽略所有任务的本地文件系统缓存。只允许读取和缓存使用远程缓存的工件。

turbo run build --team=my-team --token=xxxxxxxxxxxxxxxxx
# 用于远程缓存的承载令牌。用于在非交互式shell(例如CI/CD)中结合——team标志运行。

turbo run build --team=my-team
# 远程缓存团队的蛞蝓。与--token和--team标志结合在非交互式shell中运行时非常有用。

turbo run build --preflight
# 仅适用于配置远程工件缓存时。允许在每个缓存工件和分析请求之前发送预飞行请求。后续的上传和下载将遵循重定向。

turbo run build --trace="<trace-file-name>"
# 要查看CPU跟踪,输出跟踪到给定的文件,使用go工具trace [file]。

turbo run build --heap="<heap-file-name>"
# 要查看堆跟踪,将跟踪输出到给定文件,使用go工具pprof [file]并输入top。你也可以将它放入speedscope(在一个新选项卡中打开),并使用左重视图或三明治视图模式。

turbo run build --cpuprofile="<cpu-profile-file-name>"
# 要查看CPU配置文件,将配置文件输出到给定文件中,将文件放入speedscope

turbo run build -v
turbo run build -vv
turbo run build -vvv
# 要指定日志级别,使用-v表示信息,-vv表示调试,-vvv表示跟踪标志

入门

创建新的monorepo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ npx create-turbo@latest

>>> TURBOREPO

>>> Welcome to Turborepo! Let's get you set up with a new codebase.

? Where would you like to create your turborepo? ./my-turborepo
? Which package manager do you want to use? pnpm

>>> Created a new turborepo with the following:

- apps/web: Next.js with TypeScript
- apps/docs: Next.js with TypeScript
- packages/ui: Shared React component library
- packages/eslint-config-custom: Shared configuration (ESLint)
- packages/tsconfig: Shared TypeScript `tsconfig.json`

>>> Success! Created a new Turborepo at "my-turborepo".
Inside that directory, you can run several commands:

pnpm run build
Build all apps and packages

pnpm run dev
Develop all apps and packages

Turborepo will cache locally by default. For an additional
speed boost, enable Remote Caching with Vercel by
entering the following command:

pnpm dlx turbo login

We suggest that you begin by typing:

cd my-turborepo
pnpm dlx turbo login
  • 创建我们的工程名
  • 推荐使用pnpm 构建

turbo 会自动根据我们选择的包管理器为我们创建相对应的项目 然后我们进入项目

1
2
3
4
5
6
7
pnpm run dev
# 只会执行两个脚本——docs:dev和web:dev。这是仅有的两个指定dev的工作空间。
# 两个开发脚本同时运行,在端口3000和3001上启动Next.js应用程序。
# 在终端中,您将看到缓存绕过,强制执行。

pnpm run dev --filter docs
# 你会注意到它现在只运行docs:dev

添加到现有的monorepo

安装turbo,添加turbo作为monorepo根目录的开发依赖项。

1
pnpm add turbo -Dw

创建 turbo.json

monorepo的根目录中,创建一个名为turbo.json的空文件。这将保存Turborepo的配置。

1
2
3
{
"$schema": "https://turbo.build/schema.json"
}

其他

  • 创建管道
  • 编辑.gitignore
  • 创建package. json脚本
  • 构建 monorepo
  • 配置远程缓存

按照以上的步骤,你的项目基本已经配置完成。

最后

您现在已经启动并运行Turborepo,你可以通过上面的介绍以及下面几件事情,如果有什么疑问请以官方文档为准:

  • 了解Turborepo缓存的工作原理
  • 正确处理环境变量
  • 学会用管道编排任务运行
  • 高效筛选包任务
  • 使用CI提供商配置Turborepo

相关链接

  1. turbo官网