配置任务

Turborepo 将始终按照你在turbo.json 配置包图中描述的顺序运行任务,尽可能并行化工作以确保一切运行得尽可能快。这比一次运行一个任务要快,这也是 Turborepo 如此快速的原因之一。

例如,yarn workspaces run lint && yarn workspaces run test && yarn workspaces run build 看起来会是这样:

`turbo run lint test build` 的图形表示。它显示所有任务并行运行,脚本未运行的空白空间明显减少。

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

`turbo run lint test build` 的图形表示。它显示所有任务并行运行,脚本未运行的空白空间明显减少。

入门

turbo.json 文件是你注册 Turborepo 将运行的任务的地方。一旦你定义了任务,你就可以使用turbo run运行一个或多个任务。

  • 如果你是从头开始,我们建议使用 create-turbo 创建一个新的仓库并编辑 turbo.json 文件来尝试本指南中的代码片段。
  • 如果你正在现有仓库中采用 Turborepo,请在仓库的根目录中创建一个 turbo.json 文件。你将使用它来了解本指南中的其余配置选项。
turbo.json
package.json

定义任务

tasks 对象中的每个键都是可以由 turbo run 执行的任务。Turborepo 将在你的包中搜索与任务同名的 package.json 中的脚本

要定义任务,请在 turbo.json 中使用tasks 对象。例如,一个没有依赖项和输出的名为 build 的基本任务可能看起来像这样:

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {} // 错误!
  }
}

如果你现在运行 turbo run build,Turborepo 将并行运行所有包中的所有 build 脚本,并且不会缓存任何文件输出。**这很快就会导致错误。**你缺少一些重要的部分来使其按你期望的方式工作。

按正确顺序运行任务

dependsOn用于指定在不同任务开始运行之前必须完成的任务。例如,在大多数情况下,你希望在运行应用程序的 build 脚本之前完成库的 build 脚本。为此,你可以使用以下 turbo.json

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"] 
    }
  }
}

现在你有了你期望的构建顺序,在构建依赖项之前构建依赖者

**但要小心。**此时,你还没有标记构建输出以进行缓存。要这样做,请跳转到指定输出部分。

使用 ^ 依赖依赖项中的任务

^ 微语法告诉 Turborepo 从依赖图的底部开始运行任务。如果你的应用程序依赖于名为 ui 的库,并且该库有一个 build 任务,那么 ui 中的 build 脚本将首先运行。一旦它成功完成,你的应用程序中的 build 任务就会运行。

这是一个重要的模式,因为它确保你的应用程序的 build 任务将拥有编译所需的所有必要依赖项。当你的依赖图增长到具有多个层次的任务依赖关系的更复杂结构时,这个概念也适用。

依赖同一包中的任务

有时,你可能需要确保同一包中的两个任务按特定顺序运行。例如,你可能需要在运行库中的 test 任务之前运行该库中的 build 任务。为此,将脚本指定为 dependsOn 键中的普通字符串(不带 ^)。

Turborepo logo
./turbo.json
{
  "tasks": {
    "test": {
      "dependsOn": ["build"] 
    }
  }
}

依赖特定包中的特定任务

你还可以指定依赖于特定包中的单个任务。在下面的示例中,在运行任何 lint 任务之前必须运行 utils 中的 build 任务。

Turborepo logo
./turbo.json
{
  "tasks": {
    "lint": {
      "dependsOn": ["utils#build"] 
    }
  }
}

你也可以更具体地指定依赖任务,将其限制在某个特定的包:

Turborepo logo
./turbo.json
{
  "tasks": {
    "web#lint": {
      "dependsOn": ["utils#build"] 
    }
  }
}

使用此配置,只有在 utils 包中的 build 任务完成后,才能运行 web 包中的 lint 任务。

无依赖项

某些任务可能没有任何依赖项。例如,查找 Markdown 文件中拼写错误的任务可能不需要关心其他任务的状态。在这种情况下,你可以省略 dependsOn 键或提供一个空数组。

Turborepo logo
./turbo.json
{
  "tasks": {
    "spell-check": {
      "dependsOn": [] 
    }
  }
}

指定 outputs

Turborepo 缓存你的任务的输出,这样你就永远不会做两次相同的工作。我们将在 缓存指南 中深入讨论这个问题,但让我们先确保你的任务配置正确。

outputs 键告诉 Turborepo 在任务成功完成时应该缓存哪些文件和目录如果没有定义此键,Turborepo 将不会缓存任何文件。在后续运行中命中缓存将不会恢复任何文件输出。

以下是常见工具的输出示例:

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**"] 
    }
  }
}

Glob 模式相对于包,所以 dist/** 将处理每个包分别输出的 dist。有关构建 outputs 键的 glob 模式的更多信息,请参见glob 规范

指定 inputs

inputs 键用于指定你想要包含在任务的缓存哈希中的文件。默认情况下,Turborepo 将包含包中所有由 Git 跟踪的文件。但是,你可以使用 inputs 键更具体地指定要包含在哈希中的文件。

例如,查找 Markdown 文件中拼写错误的任务可以这样定义:

Turborepo logo
./turbo.json
{
  "tasks": {
    "spell-check": {
      "inputs": ["**/*.md", "**/*.mdx"] 
    }
  }
}

现在,只有 Markdown 文件的更改才会导致 spell-check 任务缓存未命中。

此功能会退出所有 Turborepo 的默认 inputs 行为,包括跟随源代码控制跟踪的更改。这意味着你的 .gitignore 文件将不再被遵守,你需要确保不会用你的 glob 模式捕获这些文件。

要恢复默认行为,请使用 $TURBO_DEFAULT$ 微语法

使用 $TURBO_DEFAULT$ 恢复默认值

默认的 inputs 行为通常是你想要的任务行为。但是,通过微调你的 inputs 来忽略已知不会影响任务输出的文件的更改,你可以提高某些任务的缓存命中率。

因此,你可以使用 $TURBO_DEFAULT$ 微语法来微调默认的 inputs 行为:

Turborepo logo
./turbo.json
{
  "tasks": {
    "build": {
      "inputs": ["$TURBO_DEFAULT$", "!README.md"] 
    }
  }
}

在这个任务定义中,Turborepo 将对 build 任务使用默认的 inputs 行为,但会忽略对 README.md 文件的更改。如果 README.md 文件被更改,任务仍会命中缓存。

注册根任务

你也可以使用 turbo 运行工作空间根目录中的 package.json 中的脚本。例如,你可能想要运行 lint:root 任务来处理工作空间根目录中的文件,以及每个包中的 lint 任务:

Turborepo logo
./turbo.json
{
  "tasks": {
    "lint": {
      "dependsOn": ["^lint"]
    },
    "//#lint:root": {} 
  }
}

现在根任务已注册,turbo run lint:root 将运行该任务。你也可以运行 turbo run lint lint:root 来运行所有的代码检查任务。

何时使用根任务

  • 工作空间根目录的代码检查和格式化:你可能在工作空间根目录中有需要代码检查和格式化的代码。例如,你可能想要在根目录运行 ESLint 或 Prettier。
  • 增量迁移:当你迁移到 Turborepo 时,你可能有一些还没有移动到包中的脚本。在这种情况下,你可以创建一个根任务来开始迁移,稍后再将任务分散到包中。
  • 没有包作用域的脚本:你可能有一些在特定包的上下文中没有意义的脚本。这些脚本可以注册为根任务,这样你仍然可以使用 turbo 运行它们以获得缓存、并行化和工作流程的目的。

高级用例

使用包配置

包配置是直接放在包中的 turbo.json 文件。这允许包为其自己的任务定义特定的行为,而不影响仓库的其余部分。

在有多个团队的大型单体仓库中,这允许团队对自己的任务有更大的控制权。要了解更多信息,请访问包配置文档

执行副作用

某些任务应该始终运行,无论如何,比如缓存构建后的部署脚本。对于这些任务,在你的任务定义中添加 "cache": false

Turborepo logo
./turbo.json
{
  "tasks": {
    "deploy": {
      "dependsOn": ["^build"],
      "cache": false
    },
    "build": {
      "outputs": ["dist/**"]
    }
  }
}

可以并行运行的依赖任务

某些任务尽管依赖于其他包,但可以并行运行。代码检查器就是这样的任务的一个例子,因为代码检查器不需要等待依赖项中的输出就能成功运行。

因此,你可能会倾向于这样定义你的 check-types 任务:

Turborepo logo
./turbo.json
{
  "tasks": {
    "check-types": {} // 错误!
  }
}

这会并行运行你的任务 - 但没有考虑依赖项中的源代码更改。这意味着你可以:

  1. 对你的 ui 包的接口进行破坏性更改。
  2. 运行 turbo check-types,在依赖于 ui 的应用程序包中命中缓存。

这是不正确的,因为应用程序包会显示成功的缓存命中,尽管它还没有更新以使用新的接口。在你的编辑器中手动检查应用程序包的 TypeScript 错误可能会显示错误。

因此,你对 check-types 任务定义做了一个小的更改:

Turborepo logo
./turbo.json
{
  "tasks": {
    "check-types": {
      "dependsOn": ["^check-types"] // 这样可以...但可以更快!
    }
  }
}

如果你再次测试在 ui 包中进行破坏性更改,你会注意到缓存行为现在是正确的。然而,任务不再并行运行。

要同时满足这两个要求(正确性和并行性),你可以向你的任务图引入传输节点

Turborepo logo
./turbo.json
{
  "tasks": {
    "transit": {
      "dependsOn": ["^transit"]
    },
    "check-types": {
      "dependsOn": ["transit"]
    }
  }
}

这些传输节点使用一个不匹配任何 package.json 中脚本的任务在你的包依赖关系之间创建关系。因此,你的任务可以并行运行并且意识到它们的内部依赖关系的更改。

在这个例子中,我们使用了名称 transit - 但你可以将任务命名为任何在你的工作空间中还不是脚本的名称。

下一步

配置 turbo.json 文档中还有更多选项可用,你将在接下来的指南中探索这些选项。现在,你可以开始运行一些任务来看看基础知识是如何工作的。