仓库
文档
核心概念
缓存任务

缓存任务

每个 JavaScript 或 TypeScript 代码库都需要运行 package.json 脚本,例如 buildtestlint。在 Turborepo 中,我们称这些为 **任务**。

Turborepo 可以缓存您的任务结果和日志 - 为缓慢的任务带来巨大的提速。

缓存丢失

您代码库中的每个任务都有 **输入** 和 **输出**。

  • 一个 build 任务可能具有源文件作为输入,并将日志输出到 stderrstdout 以及捆绑文件。
  • 一个 linttest 任务可能具有源文件作为输入,并将日志输出到 stdoutstderr

假设您使用 Turborepo 运行 build 任务,使用 turbo run build

  1. Turborepo 将 **评估您的任务输入** 并 **将其转换为哈希**(例如 78awdk123)。

  2. **检查本地文件系统缓存** 中是否有匹配的缓存工件(例如 ./node_modules/.cache/turbo/78awdk123.tar.zst)。

  3. 如果 Turborepo 没有找到与计算出的哈希匹配的工件,Turborepo 将 **执行任务**。

  4. 任务成功完成后,Turborepo 将 **保存所有指定的输出**(包括文件和日志)到一个新的缓存工件中,该工件由哈希寻址。

Turborepo 在创建哈希时会考虑很多信息:依赖关系图、它所依赖的任务、源文件、环境变量等等!

命中缓存

假设您再次运行任务,但没有更改任何输入

  1. **哈希将相同**,因为 **输入没有改变**(例如 78awdk123

  2. Turborepo 将找到具有匹配哈希的缓存工件(例如 ./node_modules/.cache/turbo/78awdk123.tar.zst

  3. **而不是运行任务**,Turborepo 将 **重放输出** - 将保存的日志打印到 stdout 并将保存的输出文件恢复到它们在文件系统中的相应位置。

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

关闭缓存

在某些环境中,您可能不希望写入缓存输出。要禁用缓存写入,请在任何命令后附加 --no-cache。例如,这将在所有工作区中运行 dev(以及它 dependsOn 的所有任务),但不会缓存输出。

turbo run dev --no-cache

请注意,--no-cache 禁用缓存写入,但不禁用缓存读取。如果您想禁用缓存读取,请使用 --force 标志。

您还可以通过将 pipeline.<task>.cache 配置设置为 false 来配置特定任务以跳过写入缓存。

{
  "$schema": "https://turbo.rust-lang.net.cn/schema.json",
  "pipeline": {
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

强制覆盖缓存

相反,如果您想禁用读取缓存并强制 turbo 重新执行以前缓存的任务,请添加 --force 标志。

# Run `build` npm script in all workspaces ignoring the cache.
turbo run build --force

请注意,--force 禁用缓存读取,但不禁用缓存写入。如果您想禁用缓存写入,请使用 --no-cache 标志。

处理 Node.js 版本

为了考虑 Node.js 版本,请使用 package.json 中的 engines (在新标签页中打开)。Turborepo 将看到对 package.json 的更改,并在该字段更新时错过缓存。

处理平台和其他任意哈希贡献者

对于高级用例,您可能希望操作系统 (OS)、体系结构或其他外部因素对您的哈希做出贡献。您可以通过四个简单的步骤来做到这一点。

1. 将任意文件写入磁盘

首先,创建一个脚本,该脚本考虑您感兴趣的哈希贡献者。例如,以下是一个 Node.js 脚本,它查看平台和体系结构并将这些详细信息写入文件 (turbo-cache-key.json)

#!/usr/bin/env node
 
const { writeFileSync } = require('fs');
const { join } = require('path');
 
const { platform, arch } = process;
const file = "turbo-cache-key.json";
const str = JSON.stringify({ platform, arch });
console.log(`Generating cache key: ${str}`);
writeFileSync(file, str);

2. 将文件添加到您的 .gitignore

您不希望将此文件提交到源代码管理,因为它依赖于环境。将其添加到您的 .gitignore 中。

// .gitignore

+ turbo-cache-key.json

3. 将文件添加到哈希

现在,确保 turbo 了解该文件,方法是将其添加到任务输入中。您可以通过两种方式做到这一点。

{
  "$schema": "https://turbo.rust-lang.net.cn/schema.json",
  "pipelines": {
    "build-for-platforms": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", "turbo-cache-key.json"]
    }
  }
}
{
  "$schema": "https://turbo.rust-lang.net.cn/schema.json",
  "globalDependencies": ["turbo-cache-key.json"],
  "pipelines": {
    ...
  }
}

4. 在运行 turbo 之前生成文件

最后,您需要确保在运行 turbo 之前运行脚本。例如

{
  "scripts": {
    "build-for-platforms": "node ./scripts/create-turbo-cache-key.js && turbo run build"
  }
}

turbo run build 现在将在计算 build 任务的哈希时考虑 turbo-cache-key.json 的内容。

日志

不仅 turbo 缓存了任务的输出,它还记录了终端输出(即组合的 stdoutstderr)到 (<package>/.turbo/run-<command>.log)。当 turbo 遇到缓存的任务时,它将重新播放输出,就好像它再次发生一样,但速度很快,包名称略微变暗。

哈希

到目前为止,您可能想知道 turbo 如何决定对于给定任务,什么构成缓存命中与未命中。好问题!

首先,turbo 会构建代码库当前全局状态的哈希。这包括以下内容:

  • 满足 globalDependencies 中的 glob 模式的所有文件的哈希值。
  • globalEnv 中列出的环境变量的值。
  • 来自 turbo.jsonpackage.json 和任何锁定文件的某些信息。
  • 等等!

然后,它会添加更多与给定工作区任务相关的因素。

  • 工作区文件夹中所有版本控制文件的哈希值(或与 inputs glob 匹配的文件,如果已配置)。
  • pipeline 中配置的 outputs
  • 所有已安装的 dependenciesdevDependenciesoptionalDependencies 的已解析版本集。
  • 工作区任务的名称。
  • pipeline.<task>.env 列表中指定的已排序的环境变量键值对列表。
  • 等等!

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

在执行时,任务可以使用环境变量 TURBO_HASH 获取其哈希值。此值可用于标记输出或 Dockerfile 等。