构建您的仓库

配置任务

学习如何在 turbo.json 中定义和配置任务,包括任务依赖关系、输出缓存和高级用例。

任务是 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/**"]
    }
  }
}

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 来运行所有 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": {} // 错误!
  }
}

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

  1. ui 包的接口进行破坏性更改。
  2. 运行 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 文档中有更多可用选项,您将在接下来的指南中探索。现在,您可以开始运行一些任务来了解基础知识的工作原理。