目录
Turborepo 将始终按照你的 turbo.json 配置文件 和 包图 中描述的顺序运行任务,并在可能的情况下并行处理工作,以确保一切尽可能快地运行。这比一次运行一个任务更快,也是 Turborepo 如此快速的部分原因。
包和任务图
包图
包图是由你的包管理器创建的 monorepo 的结构。当你将内部包相互安装时,Turborepo 将自动识别这些依赖关系,以构建对你的工作空间的基础理解。
这为任务图奠定了基础,你将在其中定义任务如何相互关联。
任务图
在 turbo.json 中,你表达任务如何相互关联。你可以将这些关系视为任务之间的依赖关系,但我们有一个更正式的名称来称呼它们:任务图。
须知: 你可以使用--graph 标志为你的任务生成任务图的可视化视图。
`turbo`使用Graphviz生成您的图像图,您可以从以下网址下载
Turborepo 使用一个名为有向无环图 (DAG)的数据结构来理解你的仓库及其任务。图由“节点”和“边”组成。在任务图中,节点是任务,边是任务之间的依赖关系。有向图表示连接每个节点的边都有方向,因此如果任务 A 指向任务 B,我们可以说任务 A 依赖于任务 B。边的方向取决于哪个任务依赖于哪个任务。
例如,假设你的 monorepo 有一个应用程序在 ./apps/web 中,它依赖于两个包:@repo/ui 和 @repo/utils
你还有一个 build
任务,它依赖于 ^build
{
"tasks": {
"build": {
"dependsOn": ["^build"]
}
}
}
Turborepo 将构建像这样的任务图
传递节点
构建任务图时的一个挑战是处理嵌套依赖关系。例如,假设你的 monorepo 有一个 docs 应用程序,它依赖于 ui 包,而 ui 包又依赖于 core 包
假设 docs
应用程序和 core
包各自都有一个 build
任务,但 ui
包没有。你还有一个 turbo.json
,它以与上面相同的方式配置 build
任务,使用 "dependsOn": ["^build"]
。当你运行 turbo run build
时,你期望会发生什么?
Turborepo 将构建此任务图
你可以将此图视为一系列步骤
docs
应用程序仅依赖于ui
。ui
包没有构建脚本。ui
包的依赖项有一个build
脚本,因此任务图知道要包含这些依赖项。
Turborepo 在这种情况下将 ui
包称为传递节点,因为它没有自己的 build
脚本。由于它没有 build
脚本,Turborepo 不会为其执行任何操作,但它仍然是图的一部分,目的是包含其自身的依赖项。
传递节点作为入口点
如果 docs/
包没有实现 build
任务怎么办?在这种情况下你期望会发生什么?ui
和 core
包是否仍应执行其构建任务?这里应该发生任何事情吗?
Turborepo 的心智模型是任务图中的所有节点都是相同的。换句话说,无论传递节点出现在图中的哪个位置,它们都会包含在图中。此模型可能会产生意想不到的后果。例如,假设你已将 build
任务配置为依赖于 ^test
{
"tasks": {
"build": {
"dependsOn": ["^test"]
}
}
}
假设你的 monorepo 有许多应用程序和许多包。所有包都有 test
任务,但只有一个应用程序有 build
任务。Turborepo 的心智模型表示,当你运行 turbo run build
时,即使应用程序未实现 build
,所有作为依赖项的包的 test
任务也会显示在图中。
配置任务
例如,yarn workspaces run lint && yarn workspaces run test && yarn workspaces run build 看起来像这样
但是,为了使用 Turborepo 更快地完成相同的工作,你可以使用 turbo run lint test build
开始使用
根目录的 turbo.json 文件是你注册 Turborepo 将要运行的任务的地方。一旦你定义了你的任务,你就可以使用 turbo run 运行一个或多个任务。
- 如果你是全新开始,我们建议 使用 create-turbo 创建一个新的仓库 并编辑 turbo.json 文件来试用本指南中的代码片段。
- 如果你要在现有仓库中采用 Turborepo,请在你的仓库根目录中创建一个 turbo.json 文件。你将使用它来了解本指南中其余的配置选项。
定义任务
tasks 对象中的每个键都是可以通过 turbo run 执行的任务。Turborepo 将在你的包中搜索 package.json 中脚本名称与任务名称相同的脚本。
tasks 对象中的每个键都是一个任务的名称,可以通过 turbo run 执行。 Turborepo 将在你的 Workspace 配置 中描述的包中搜索 package.json 中具有任务名称的脚本。
使用任务中描述的其余配置,Turborepo 将按描述的顺序运行脚本,并在提供时将日志和文件输出缓存在 outputs 键 中。
要定义一个任务,请使用 turbo.json 中的 tasks 对象。例如,一个名为 build 的基本任务,没有依赖项和输出,可能看起来像这样
{
"tasks": {
"build": {} // Incorrect!
}
}
如果你此时运行 turbo run build
,Turborepo 将并行运行你的包中的所有 build
脚本,并且不会缓存任何文件输出。这会很快导致错误。 你还缺少一些重要的部分,才能使其按你期望的方式工作。
以正确的顺序运行任务
dependsOn 键 用于指定在另一个任务开始运行之前必须完成的任务。例如,在大多数情况下,你希望你的库的 build 脚本在你的应用程序的 build 脚本运行之前完成。为此,你可以使用以下 turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"]
}
}
}
你现在拥有了你期望的构建顺序,在 依赖项 之前构建 依赖者。
但要小心。 此时,你还没有标记用于缓存的构建输出。
依赖于依赖项中的任务,使用 ^
^ 微语法告诉 Turborepo 从依赖关系图的底部开始运行任务。如果你的应用程序依赖于一个名为 ui 的库,并且该库有一个 build 任务,那么 ui 中的 build 脚本将首先运行。一旦它成功完成,你的应用程序中的 build 任务将运行。
这是一个重要的模式,因为它确保你的应用程序的 build 任务将拥有编译所需的所有必要依赖项。当你的依赖关系图增长到具有更多级别的任务依赖关系的更复杂结构时,此概念也适用。
依赖于同一包中的任务
有时,你可能需要确保同一包中的两个任务以特定顺序运行。例如,你可能需要在你的库中运行 build 任务,然后再在同一库中运行 test 任务。为此,请在 dependsOn 键中将脚本指定为纯字符串(不带 ^)。
{
"tasks": {
"test": {
"dependsOn": ["build"]
}
}
}
依赖于特定包中的特定任务
你还可以指定要依赖的特定包中的单个任务。在下面的示例中,utils 中的 build 任务必须在任何 lint 任务之前运行。
"tasks": {
"lint": {
"dependsOn": ["utils#build"]
}
}
}
你也可以更具体地说明依赖任务,将其限制为某个特定的包
{
"tasks": {
"web#lint": {
"dependsOn": ["utils#build"]
}
}
}
使用此配置,只有在 utils 包中的 build 任务完成后,才能运行你的 web 包中的 lint 任务。
没有依赖项
某些任务可能没有任何依赖项。例如,用于查找 Markdown 文件中拼写错误的任务可能不需要关心其他任务的状态。在这种情况下,你可以省略 dependsOn 键或提供一个空数组。
{
"tasks": {
"spell-check": {
"dependsOn": []
}
}
}
指定 outputs
Turborepo 缓存你的任务的输出,这样你就永远不会做两次相同的工作。我们将在 缓存指南 中深入讨论这一点,但让我们首先确保你的任务已正确配置。
outputs 键告诉 Turborepo 在任务成功完成后应该缓存的 文件和目录。如果没有定义此键,Turborepo 将不会缓存任何文件。在后续运行中命中缓存将不会恢复任何文件输出。
以下是一些常见工具的输出示例
{
"tasks": {
"build": {
"outputs": ["dist/**"],
}
}
}
Glob 相对于包,因此 dist/** 将分别处理每个包输出的 dist。有关为 outputs 键构建 glob 模式的更多信息,请参阅 glob 规范。
指定 inputs
inputs 键用于指定你想要包含在任务哈希中的文件,以便进行 缓存。默认情况下,Turborepo 将包括包中 Git 跟踪的所有文件。但是,你可以使用 inputs 键更具体地说明哪些文件包含在哈希中。
例如,用于查找 Markdown 文件中拼写错误的任务可以像这样定义
{
"tasks": {
"spell-check": {
"inputs": ["**/*.md", "**/*.mdx"]
}
}
}
现在,只有 Markdown 文件中的更改才会导致 spell-check
任务错过缓存。
此功能选择退出 Turborepo 的所有默认 inputs 行为,包括跟踪源代码控制所跟踪的更改。这意味着你的 .gitignore 文件将不再被尊重,你需要确保你不会使用 glob 捕获这些文件。要恢复默认行为,请使用 $TURBO_DEFAULT$ 微语法。
使用 $TURBO_DEFAULT$ 恢复默认值
默认的 inputs 行为 通常是你的任务所需要的。但是,你可以通过微调你的 inputs 以忽略已知不会影响任务输出的文件更改,从而提高你的缓存命中率。
因此,你可以使用 $TURBO_DEFAULT$
微语法来微调默认的 inputs
行为
{
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", "!README.md"]
}
}
}
在此任务定义中,Turborepo 将对 build
任务使用默认的 inputs
行为,但将忽略对 README.md
文件的更改。如果 README.md
文件被更改,该任务仍然会命中缓存。
注册根任务
你还可以使用 turbo 在工作区根目录的 package.json 中运行脚本。例如,你可能想要为工作区根目录中的文件运行 lint:root 任务,以及每个包中的 lint 任务
{
"tasks": {
"lint": {
"dependsOn": ["^lint"]
},
"//#lint:root": {}
}
}
现在已注册根任务,turbo run lint:root
现在将运行该任务。你还可以运行 turbo run lint lint:root
来运行你所有的 lint 任务。
- 工作区根目录的 Lint 和格式化:你可能在工作区根目录中有一些代码想要进行 lint 和格式化。例如,你可能想在你的根目录中运行 ESLint 或 Prettier。
- 增量迁移:当你迁移到 Turborepo 时,你可能有一个中间步骤,其中你有一些尚未移动到包的脚本。在这种情况下,你可以创建一个根任务来开始迁移,并在稍后将任务分散到包中。
- 没有包范围的脚本:你可能有一些在特定包的上下文中没有意义的脚本。这些脚本可以注册为根任务,这样你仍然可以使用
turbo
运行它们,以实现缓存、并行化和工作流目的。
具有运行时依赖的长时任务
你可能有一个长时间运行的任务,需要另一个任务始终同时运行。为此,请使用 with 键。
将与此任务并行运行的任务列表。 这对于你希望确保始终同时运行的长时间运行的任务最有用。
{
"tasks": {
"dev": {
"with": ["api#dev"],
"persistent": true,
"cache": false
}
}
}
长时间运行的任务永远不会退出,这意味着你不能依赖它。相反,with
关键字将在 web#dev
任务运行时运行 api#dev
任务。
执行副作用
某些任务应该始终运行,无论如何,例如缓存构建后的部署脚本。对于这些任务,请在你的任务定义中添加 "cache": false。
定义是否应缓存任务输出。 将
cache
设置为 false 对于长时间运行的开发任务以及确保任务始终在其任务执行图中运行时非常有用。
{
"tasks": {
"deploy": {
"dependsOn": ["^build"],
"cache": false
},
"build": {
"outputs": ["dist/**"]
}
}
}