配置任务
任务是 Turborepo 运行的脚本。您可以在 turbo.json 配置和包图中表达任务之间的关系。
Turborepo 将始终并行化任何可以并行化的工作,以确保一切运行得尽可能快。这比一次运行一个任务要快,这也是 Turborepo 如此快速的原因之一。
例如,yarn workspaces run lint && yarn workspaces run build && yarn workspaces run test 看起来像这样:

但是,要使用 Turborepo 更快地完成相同的工作,您可以使用 turbo run lint build test:

入门
根目录的 turbo.json 文件是您注册 Turborepo 将运行的任务的地方。一旦定义了任务,您就可以使用 turbo run 运行一个或多个任务。
- 如果您从头开始,我们建议使用
create-turbo创建新的代码库并编辑turbo.json文件来尝试本指南中的代码片段。 - 如果您在现有代码库中采用 Turborepo,请在代码库根目录创建一个
turbo.json文件。您将使用它来学习本指南中其余的配置选项。
turbo.json (新建)
package.json
apps/
packages/
定义任务
tasks 对象中的每个键都是可以由 turbo run 执行的任务。Turborepo 将在您的包中搜索与任务同名的 package.json 中的脚本。
要定义任务,请在 turbo.json 中使用 tasks 对象。例如,一个没有依赖项和没有输出的名为 build 的基本任务可能如下所示:
{
"tasks": {
"build": {} // 错误!
}
}
如果您此时运行 turbo run build,Turborepo 将并行运行包中的所有 build 脚本,并且不会缓存任何文件输出。这将很快导致错误。 您缺少一些重要的部分来使其按预期工作。
以正确的顺序运行任务
dependsOn 键用于指定在不同任务开始运行之前必须完成的任务。例如,在大多数情况下,您希望库的 build 脚本在应用程序的 build 脚本运行之前完成。为此,您可以使用以下 turbo.json:
{
"tasks": {
"build": {
"dependsOn": ["^build"]
}
}
}
您现在有了预期的构建顺序,在_依赖者_之前构建_依赖项_。
但要小心。 此时,您还没有标记构建输出用于缓存。要这样做,请跳转到指定输出部分。
使用 ^ 依赖于依赖项中的任务
^ 微语法告诉 Turborepo 在目标包之前在直接依赖项中运行任务。如果您的应用程序依赖于名为 ui 的库,并且该库有一个 build 任务,则 ui 中的 build 脚本将首先运行。一旦成功完成,应用程序中的 build 任务将运行。
这是一个重要的模式,因为它确保您的应用程序的 build 任务将拥有编译所需的所有必要依赖项。随着您的依赖图增长到具有多级任务依赖项的更复杂结构,这个概念也适用。
依赖于同一包中的任务
有时,您可能需要确保同一包中的两个任务以特定顺序运行。例如,您可能需要在同一库中运行 test 任务之前运行 build 任务。为此,请在 dependsOn 键中将脚本指定为纯字符串(不带 ^)。
{
"tasks": {
"test": {
"dependsOn": ["build"]
}
}
}
依赖于特定包中的特定任务
您还可以指定依赖于特定包中的单个任务。在下面的示例中,必须在任何 lint 任务之前运行 utils 中的 build 任务。
{
"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": [".next/**", "!.next/cache/**"]
}
}
}
{
"tasks": {
"build": {
"outputs": ["dist/**"]
}
}
}
{
"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": {}
}
}
{
"scripts": {
"lint": "turbo run lint lint:root",
"lint:root": "eslint ."
}
}
注册根任务后,turbo run lint:root 现在将运行该任务。您还可以运行 turbo run lint lint:root 来运行所有 linting 任务。
何时使用根任务
- 工作区根目录的 linting 和格式化:您可能在工作区根目录中有想要 lint 和格式化的代码。例如,您可能想在根目录中运行 ESLint 或 Prettier。
- 增量迁移:在迁移到 Turborepo 时,您可能有一个中间步骤,其中有一些尚未移动到包中的脚本。在这种情况下,您可以创建根任务来开始迁移,稍后将任务分散到包中。
- 没有包范围的脚本:您可能有一些在特定包上下文中没有意义的脚本。这些脚本可以注册为根任务,这样您仍然可以使用
turbo运行它们以进行缓存、并行化和工作流程目的。
高级用例
使用包配置
包配置是直接放置在包中的 turbo.json 文件。这允许包为其自己的任务定义特定行为,而不影响代码库的其余部分。
在有许多团队的大型 monorepo 中,这允许团队为自己的任务有更大的控制权。要了解更多信息,请访问包配置文档
具有运行时依赖项的长时间运行任务
您可能有一个长时间运行的任务,需要另一个任务始终同时运行。为此,请使用 with 键。
{
"tasks": {
"dev": {
"with": ["api#dev"],
"persistent": true,
"cache": false
}
}
}
长时间运行的任务永远不会退出,这意味着您无法依赖它。相反,with 关键字将在 web#dev 任务运行时运行 api#dev 任务。
执行副作用
某些任务应该始终运行,无论如何,比如缓存构建后的部署脚本。对于这些任务,请在任务定义中添加 "cache": false。
{
"tasks": {
"deploy": {
"dependsOn": ["^build"]
},
"build": {
"outputs": ["dist/**"]
}
}
}
可以并行运行的依赖任务
尽管依赖于其他包,某些任务仍可以并行运行。符合此描述的任务示例是 linter,因为 linter 不需要等待依赖项中的输出即可成功运行。
因此,您可能会想这样定义您的 check-types 任务:
{
"tasks": {
"check-types": {} // 错误!
}
}
这会并行运行您的任务 - 但不会考虑依赖项中的源代码更改。这意味着您可以:
- 对
ui包的接口进行破坏性更改。 - 运行
turbo check-types,在依赖于ui的应用程序包中命中缓存。
这是不正确的,因为应用程序包将显示成功的缓存命中,尽管没有更新以使用新接口。在编辑器中手动检查应用程序包中的 TypeScript 错误可能会显示错误。
因此,您对 check-types 任务定义进行小的更改:
{
"tasks": {
"check-types": {
"dependsOn": ["^check-types"] // 这有效...但可能更快!
}
}
}
如果您再次测试在 ui 包中进行破坏性更改,您会注意到缓存行为现在是正确的。但是,任务不再并行运行。
要满足两个要求(正确性和并行性),您可以向任务图引入传输节点:
{
"tasks": {
"transit": {
"dependsOn": ["^transit"]
},
"check-types": {
"dependsOn": ["transit"]
}
}
}
这些传输节点使用不执行任何操作的任务在包依赖项之间创建关系,因为它不匹配任何 package.json 中的脚本。因此,您的任务可以并行运行并且了解其内部依赖项的更改。
在此示例中,我们使用了名称 transit - 但您可以将任务命名为任何不是工作区中已有脚本的名称。
下一步
在配置 turbo.json 文档中有更多可用选项,您将在接下来的指南中探索。现在,您可以开始运行一些任务来了解基础知识的工作原理。