Turborepo 缓存机制全解析:从缓存方程到 global.inputs 的哈希深入指南
2026/9/20 21:14:06 网站建设 项目流程

Turborepo 缓存机制全解析:从缓存方程到 global.inputs 的哈希深入指南

【免费下载链接】turboBuild system optimized for JavaScript and TypeScript, written in Rust项目地址: https://gitcode.com/gh_mirrors/tu/turbo

Turborepo 是一个用 Rust 编写的 JavaScript/TypeScript 构建系统优化工具,其最核心的设计原则是绝不重复做同样的工作(never do the same work twice)。本文以 skills/turborepo/references/caching/RULE.md 为骨架,结合仓库内 Rust 源码(turborepo-hashturborepo-cache)与集成测试(global_inputs_test.rs),系统讲解 Turborepo 缓存的工作机制:缓存方程如何计算、全局哈希与任务哈希的输入有哪些、global.inputs如何改变哈希方程、缓存中存储什么、缓存放哪里、命中后如何恢复。读完本文,你将能准确判断"什么改动会让缓存失效"这一 Turborepo 使用中最关键的问题,并能利用futureFlags.globalConfiguration精确控制全局文件的缓存粒度。

缓存方程:fingerprint(inputs) → stored outputs

Turborepo 缓存机制可以用一条方程概括:

fingerprint(inputs) → stored outputs

如果某次任务的输入与上次完全一致(哈希相同),Turborepo 就直接从缓存恢复输出,而不重新执行任务。这里的"输入"是一组经过哈希计算得到的指纹(fingerprint),"输出"则是构建产物与日志。

缓存键由两层哈希共同决定:

task cache key = hash(global hash, task hash)
  • global hash(全局哈希):影响仓库中所有任务的公共输入。
  • task hash(任务哈希):只影响某个具体任务的输入。

只有两层哈希都未变化,缓存才会命中。这一方程在源码中有直接对应:crates/turborepo-hash/src/lib.rs中定义了GlobalHashable(承载全局哈希输入)与TaskHashable(承载任务哈希输入)两个结构体,其中TaskHashable明确包含global_hash字段,任务哈希计算时会把全局哈希一并卷入。

全局哈希输入:影响所有任务的公共因子

以下输入会进入全局哈希,任何一项变化都会使仓库内所有任务的缓存失效:

  1. 锁文件package-lock.jsonyarn.lockpnpm-lock.yamlbun.lock。注意:二进制格式的bun.lockb不受支持,需要先运行bun install --save-text-lockfile将其转换为文本格式。
  2. globalDependencies中列出的文件(使用globalConfiguration时改用global.inputs,见下文)。
  3. globalEnv中列出的环境变量(使用globalConfiguration时改用global.env)。
  4. turbo.json配置本身

配置示例:

{ "globalDependencies": [".env", "tsconfig.base.json"], "globalEnv": ["CI", "NODE_ENV"] }

从源码看,锁文件并非直接以文件哈希进入全局哈希,而是经过结构化处理crates/turborepo-hash/src/lib.rsLockFilePackages会提取锁文件中每个包的keyversion,再通过turborepo_lockfile_hash做规范化序列化。这意味着锁文件内容的无关变化(如注释、格式调整)不会影响缓存,只有包集合或版本变化才真正触发全局失效。

GlobalHashable结构体还揭示了全局哈希的其他输入,包括:global_file_hash_map(全局依赖文件哈希映射)、root_external_dependencies_hash(根外部依赖哈希)、root_internal_dependencies_hash(根内部依赖哈希)、engines(Node 等引擎版本)、envresolved_env_varsframework_inference(框架推断开关)以及global_configuration(是否启用 globalConfiguration 模式)。

任务哈希输入:决定单个任务缓存的因子

以下输入会进入单个任务的任务哈希:

  1. 包内所有文件(除非被inputs过滤)。
  2. package.json内容
  3. 任务env键中列出的环境变量
  4. 任务配置本身:命令(command)、outputs、依赖关系。
  5. 依赖任务的哈希(由dependsOn决定,包括^build这类"依赖包任务")。
  6. 使用futureFlags.globalConfiguration时,global.inputs中的文件也会并入任务输入(见下一节)。

配置示例:

{ "tasks": { "build": { "dependsOn": ["^build"], "inputs": ["src/**", "package.json", "tsconfig.json"], "env": ["API_URL"] } } }

源码中TaskHashable结构体逐一对应了这些输入:task_dependency_hashes(依赖任务哈希)、hash_of_files(包内文件哈希)、external_deps_hash(外部依赖哈希)、outputspass_through_argsenvresolved_env_varscommand_override等。值得注意的细节是:任务的实际执行命令(command override)也参与哈希——从源码注释看,"改变任务实际运行的命令必须使其缓存结果失效",这保证了改动命令不会命中陈旧缓存。

global.inputs:如何改变哈希方程

当在turbo.json中启用futureFlags.globalConfiguration后,global.inputs的行为与globalDependencies根本性差异global.inputs中的文件不再进入全局哈希,而是被前置到每个任务的inputs中,折叠进任务哈希

使用globalDependencies(默认模式):

task cache key = hash(global hash, task hash) ↑ 包含 globalDependencies 文件哈希

在这种模式下,修改globalDependencies中的任何文件都会使每一个任务失效,无论任务级inputs如何设置,任务都无法选择退出。集成测试 crates/turborepo/tests/global_inputs_test.rs 中的test_global_dependencies_cannot_be_excluded_by_task_inputs正是验证了这一点:测试注释明确指出"即使任务用取反 glob 明确排除了该文件,取反也没有效果,因为文件位于全局哈希而非任务输入中"。

使用global.inputsfutureFlags.globalConfiguration模式):

task cache key = hash(global hash, task hash) ↑ 包含 global.inputs 文件哈希(与任务 inputs 合并)

global.inputs的文件会合并进每个任务的输入 glob。这意味着:

  • 任务可以用取反 glob 排除特定全局文件
"inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"]
  • 全局哈希变小(它仍包含锁文件、engines、global.env等,但不再包含global.inputs文件的文件哈希)。
  • 任务哈希正确地把全局输入文件哈希与任务自身输入合并在一起

完整配置示例:

{ "futureFlags": { "globalConfiguration": true }, "global": { "inputs": ["tsconfig.json", ".env"] }, "tasks": { "build": { "outputs": ["dist/**"] }, "lint": { "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"] } } }

在这个例子中,修改tsconfig.json会使build失效(它位于该任务输入中),但不会使lint失效(它被显式排除)。而在globalDependencies模式下,两者都会被失效。

这段行为差异同样有集成测试佐证:global_inputs_test.rs中的test_global_inputs_can_be_excluded_and_affect_tasks验证了启用globalConfiguration后,排除config.txt的任务(app-b)在第三次构建时命中缓存(输出cache hit),而未排除的任务(app-a)则正常失效重跑。

这里涉及两个魔法变量,说明如下:

  • $TURBO_DEFAULT$:展开为"包内全部文件"的默认输入集合(相当于未写inputs时的默认行为),在自定义inputs时用它作为基线再叠加增删。
  • $TURBO_ROOT$:指向 monorepo 仓库根目录的绝对路径,用于书写!$TURBO_ROOT$/tsconfig.json这类跨包取反表达式。

缓存中存储什么

Turborepo 为每个任务缓存两类内容:

  1. 文件输出outputs中指定的文件/目录。
  2. 任务日志:stdout/stderr,供缓存命中时重放。
{ "tasks": { "build": { "outputs": ["dist/**", ".next/**"] } } }

关键要点:

  • 缓存是内容寻址的(基于输入哈希,而非时间戳)。
  • outputs数组表示任务照常运行但什么都不缓存
  • 没有outputs键的任务不缓存任何东西(如需显式表达,用"outputs": [])。

本地缓存位置

默认情况下,本地缓存存放在仓库根目录下:

.turbo/cache/ ├── <hash1>.tar.zst # 压缩后的输出 ├── <hash2>.tar.zst └── ...

请务必将.turbo加入.gitignore,避免缓存目录进入版本控制。

源码层面,本地缓存由crates/turborepo-cache/src/fs.rs中的FSCache实现:fetch方法按{hash}.tar.zst拼接缓存路径并检查文件是否存在;每个缓存归档还配有一个{hash}-manifest.json清单文件与-meta.json元数据(记录 hash、duration、git sha 等)。FSCache::new在初始化时通过create_dir_all确保缓存目录存在。整个 crate 的文档注释(crates/turborepo-cache/src/lib.rs)明确指出:底层缓存产物是gzip 压缩的 tarball.tar.zst即 zstd 压缩的 tar 包)。

此外,crates/turborepo-cache/src/cache_archive/目录下按职责拆分了create.rs(归档写入)、restore.rs(归档恢复)、restore_manifest.rs(恢复清单)等模块,其中RestoreManifest支持快速路径校验:若清单存在且磁盘上所有文件仍然匹配,可跳过解压 tar 直接判定命中。

缓存恢复流程

缓存命中时,Turborepo 依次执行:

  1. 解压归档输出到原始位置restore相关模块负责把 tar 中的文件恢复到各自路径)。
  2. 重放记录的 stdout/stderr日志。
  3. 将任务标记为 cached(在输出中显示FULL TURBO)。

fs.rsfetch实现还能看到一个性能优化细节:恢复时若-manifest.json存在且磁盘上所有文件都仍然匹配manifest.validate_all(anchor)),则完全跳过打开与解压 tar 的步骤——这就是缓存"快速路径"。

示例流程:从 cache miss 到 FULL TURBO

# 第一次运行 - 执行 build,并缓存结果 turbo build # → packages/ui: cache miss, executing... # → packages/web: cache miss, executing... # 第二次运行 - 输入未变,从缓存恢复 turbo build # → packages/ui: cache hit, replaying output # → packages/web: cache hit, replaying output # → FULL TURBO

关键要点速查

  • 缓存是内容寻址的(基于输入哈希,而不是时间戳)。
  • outputs数组意味着任务会运行,但不缓存任何东西
  • 没有outputs键的任务同样不缓存(如需显式表达,请写"outputs": [])。
  • 任何输入变化都会使缓存失效——这是缓存正确性的基石。
  • 全局哈希与任务哈希共同决定缓存键:task cache key = hash(global hash, task hash)
  • globalDependencies模式下任务无法排除全局文件;启用futureFlags.globalConfiguration后,global.inputs会并入任务输入,从而允许用取反 glob(如!$TURBO_ROOT$/tsconfig.json)精确控制失效范围。

进一步阅读

  • 本文骨架文档:skills/turborepo/references/caching/RULE.md
  • 缓存常见陷阱:skills/turborepo/references/caching/gotchas.md
  • 远程缓存:skills/turborepo/references/caching/remote-cache.md
  • 哈希实现:crates/turborepo-hash/src/lib.rs(全局哈希/任务哈希结构体与 Cap'n Proto 序列化)
  • 缓存实现:crates/turborepo-cache/src/fs.rscrates/turborepo-cache/src/lib.rscrates/turborepo-cache/src/cache_archive/
  • 缓存配置解析(local:rw,remote:r等):crates/turborepo-cache/src/config.rs
  • global.inputs 行为验证测试:crates/turborepo/tests/global_inputs_test.rs

【免费下载链接】turboBuild system optimized for JavaScript and TypeScript, written in Rust项目地址: https://gitcode.com/gh_mirrors/tu/turbo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询