Turborepo 高性能构建系统
Turborepo 是一个用于 JavaScript 和 TypeScript monorepos 的高性能构建系统。Turborepo 重新设计了 Facebook 和谷歌使用的构建系统技术,以消除维护负担和开销。
特性:
- 增量构建: 构建一次已经足够痛苦了,
Turborepo会记住您构建的内容,并跳过已经计算过的内容。 - 内容
hash:Turborepo查看您的文件内容,而不是时间戳来找出需要构建的内容。 - 远程缓存:与您的团队和
CI/CD共享远程构建缓存以获得更快的构建。 - 并行执行:在不浪费空闲
cpu的情况下,以最大的并行度使用每个核执行构建。 - 零运行时开销:
Turborepo不会干扰您的运行时代码或触及您的源代码映射。 - 删减子集:通过生成一个只需要构建特定目标的
monorepo的子集来加快PaaS的部署速度 - 任务管道:定义任务之间的关系,然后让
Turborepo优化要构建的内容和时间。 - 基于约定的配置:通过约定来降低配置的复杂度,只需要几行简单的
JSON就能完成配置 - 浏览器中的配置文件:生成构建配置文件并在
Chrome或Edge中导入,以了解哪些任务花费的时间最长
缓存任务
每个JavaScript或TypeScript代码库都需要运行package.json脚本,如build, test和lint。在Turborepo中,我们称之为任务。
Turborepo可以缓存任务的结果和日志-对于慢速任务可以极大地加速。
丢失缓存
假设你在Turborepo中使用turbo run build运行一个构建任务:

Turborepo将计算任务的输入(默认是工作区文件夹中的所有非git忽略文件)并将它们转换为哈希值(例如78awdk123)。- 检查本地文件系统缓存中以散列命名的文件夹(例如
../node_modules/.cache/turbo/78awdk123)。 - 如果
Turborepo没有为计算的散列找到任何匹配的工件,那么Turborepo将执行该任务。 - 任务结束后,
Turborepo将所有输出(包括文件和日志)保存到哈希下的缓存中。
命中缓存
假设你在不改变任何输入的情况下再次运行任务:
- 散列将是相同的,因为输入没有改变(例如
78awdk123) Turborepo将在其缓存中找到带有计算散列的文件夹(例如:/node_modules/.cache/turbo/78awdk123)Turborepo不运行任务,而是重播输出——将保存的日志打印到标准输出,并将保存的输出文件恢复到文件系统中各自的位置。
从缓存中恢复文件和日志几乎是瞬间发生的。这可以将构建时间从几分钟或几小时缩短到几秒或几毫秒。尽管具体的结果会根据代码库依赖关系图的形状和粒度而有所不同,但大多数团队发现,使用Turborepo的缓存,他们可以将每月的总体构建时间缩短约40-85%。
配置缓存输出
使用管道,您可以在Turborepo中配置缓存约定。
要覆盖默认的缓存输出行为,将一个glob数组传递给管道。输出数组。任何满足任务glob模式的文件都将被视为工件。
如果你的任务不产生任何文件(例如使用jest进行单元测试),将输出设置为空数组(即[]),Turborepo将为你缓存日志。
当您运行turbo运行构建测试时,Turborepo将执行您的构建和测试脚本,并将其输出缓存到./node_modules/.cache/turbo中。
配置缓存输入
当工作空间中的任何文件发生更改时,就认为该工作空间已经更新。但是,对于某些任务,我们只希望在相关文件发生更改时重新运行该任务。指定输入可以让我们定义哪些文件与特定任务相关。例如,下面的测试配置声明,测试任务只需要
在src/和test/子目录中的.tsx或.ts文件自上次执行以来发生更改时才需要执行。
1 | { |
关闭高速缓存
1 | turbo run dev --parallel --no-cache |
1 | { |
基于文件更改缓存
对于某些任务,如果不相关的文件发生更改,您可能不希望缓存丢失。例如,更新README.md可能不需要为测试任务触发缓存缺失。您可以使用输入来限制turbo为特定任务考虑的文件集。
在这种情况下,只考虑与确定测试任务上的缓存命中相关的.ts和.tsx文件:
1 | { |
基于环境变量修改缓存
你可以根据环境变量的值来控制turbo的缓存行为:
在管道定义中的env键中包含环境变量将影响每个任务或每个工作空间任务的缓存指纹。
任何在其名称中包含THASH的环境变量的值都会影响所有任务的缓存指纹。
强制覆盖缓存
相反,如果你想禁止读取缓存并强制turbo重新执行之前缓存的任务,添加--force标志:
1 | # Run `build` npm script in all workspaces, |
日志
turbo不仅缓存任务的输出,它还记录终端输出(即结合stdout和stderr)到(<package>/.turbo/run-<command>.log)。当turbo遇到一个缓存的任务时,它会重新播放输出,就像它再次发生一样,但是是即时的,包的名称略微暗淡。
哈希
您可能想知道turbo如何决定给定任务的缓存命中和未命中。
- 首先,
turbo构造了代码库当前全局状态的哈希值 - 然后它添加了更多与给定工作空间任务相关的因素
一旦turbo在执行过程中遇到给定工作区的任务,它就会检查缓存(本地和远程)以查找匹配的散列。如果匹配,则跳过执行该任务,将缓存的输出移动或下载到合适的位置,并立即重放先前记录的日志。如果缓存(本地或远程)中没有任何与计算的哈希匹配的内容,turbo将在本地执行任务,然后使用哈希作为索引缓存指定的输出。
给定任务的哈希值在执行时作为环境变量TURBO_HASH注入。这个值在打印输出或标记Dockerfile等方面很有用。
远程缓存
Turborepo的任务缓存可以节省大量时间,因为它不会重复同样的工作。
但是有一个问题-缓存是本地的。当您使用CI时,这可能会导致大量重复工作
因为默认情况下Turborepo只缓存到本地文件系统,所以相同的任务(Turbo运行构建)必须在每台机器上(由您、您的队友、您的CI、您的PaaS等)重新执行,即使所有的任务输入都是相同的–这浪费了时间和资源。
远程缓存是
Turborepo的一个强大特性,但是能力越大责任越大。首先要确保缓存正确,并仔细检查环境变量的处理情况。还请记住,Turborepo将日志视为工件,因此要注意打印到控制台的内容。
一个单一的共享缓存
通过与Vercel等提供商合作,Turborepo可以安全地与远程缓存(存储任务结果的云服务器)通信。
这可以通过防止整个组织中的重复工作来节省大量的时间。
Vercel
本地开发,如果您想将本地turborepo链接到远程缓存,请先使用您的Vercel帐户验证turborepo CLI:
1 | npx turbo login |
启用后,对当前正在缓存的工作空间进行一些更改,并使用turbo run对其运行任务。您的缓存工件现在将存储在本地和远程缓存中。
为了验证,删除您的本地Turborepo缓存:
然后再次运行相同的构建。如果一切正常,turbo不应该在本地执行任务,而是从远程缓存下载日志和工件,并将它们重放给您。
自定义远程缓存
您可以自行托管自己的远程缓存或使用其他远程缓存服务提供商,只要他们符合Turborepo的远程缓存服务器API。
您可以通过指定--api和--token标志来设置远程缓存域,其中--api是主机名,--token是承载令牌。
1 | turbo run build --api="https://my-server.example.com" --token="xxxxxxxxxxxxxxxxx" |
您可以在这里看到所需的端点/请求在一个新选项卡中打开。
拓扑
拓扑排序通常用来“排序”具有依赖关系的任务, 如果 A 依赖于 B,B 依赖于 C,则拓扑顺序为 C、B、A。
配置文件turbo.json
在monorepo的根目录中,创建一个名为turbo.json的空文件。这将保持Turborepo的配置。
要定义monorepo的任务依赖关系图,请使用turbo中的pipeline键。Turbo解释这个配置,以优化调度、执行和缓存每个包的输出。在工作空间中定义的Json脚本。
./turbo.json:
1 | { |
全局依赖(globalDependencies)
用于隐式全局哈希依赖的文件glob列表。这些文件的内容将包含在全局哈希算法中,并影响所有任务的哈希。
这对于基于.env文件(不在Git中)或任何影响工作空间任务的根级文件(但在传统的依赖关系图中没有表示)破坏缓存非常有用(例如根tsconfig)。Json, jest.config.js, .eslintrc等))。
例子:
1 | { |
全局环境(globalEnv)
用于隐式全局哈希依赖关系的环境变量列表。这些环境变量的内容将包含在全局哈希算法中,并影响所有任务的哈希值。
1 | { |
管道(pipeline)
表示项目的任务依赖关系图的对象。Turbo解释这些约定,以正确地调度、执行和缓存项目中任务的输出。
管道对象中的每个键都是turbo运行可以执行的任务的名称。如果turbo找到一个带有包的工作空间。对象,它将在执行期间将管道任务配置应用到该NPM脚本。这允许您使用管道在整个Turborepo中设置约定。
依赖于(DependsOn)
在dependsOn中的一个项目前面加上^,告诉turbo这个管道任务依赖于工作空间的拓扑依赖关系,首先用^前缀完成任务(例如:“一个工作空间的构建任务应该只在它的所有依赖项和devDependencies完成了它们自己的构建命令后运行”)。
dependsOn中没有^前缀的项表示工作空间级别任务之间的关系(例如:“工作区的test和lint命令依赖于build首先完成”)。
在dependsOn中的项前面加上$可以告诉turbo这个管道任务依赖于该环境变量的值。
不建议在
dependsOn配置中使用$声明环境变量。请使用env键。
例子:
1 | { |
- 空依赖: 如果一个任务的
dependsOn为[]或者不声明这个属性,那么表明这个任务可以在任意时间被执行
env
类型:string []
任务所依赖的环境变量列表。
例子:
1 | { |
outputs
类型:string []
默认为["dist/**", "build/**"]。任务的可缓存文件系统输出的glob模式集。
1 | { |
Cache
默认为true。是否缓存任务输出。将缓存设置为false对于不希望缓存的守护进程或长时间运行的“监视”或开发模式任务非常有用。
例子:
1 | { |
inputs
默认为[]。告诉turbo在确定工作空间是否为特定任务更改时要考虑的文件集。将此设置为一个glob列表将导致仅当匹配这些glob的文件发生更改时才重新运行任务。例如,如果您希望跳过运行测试,除非源文件发生了更改,那么这将很有帮助。
指定[]将导致在工作区中的任何文件更改时重新运行任务
例子:
1 | { |
outputMode
outputMode代表输出的模式类型是字符串
full:默认值。显示所有输出hash-only:只显示任务的哈希值new-only:只显示缓存未命中的输出none:隐藏所有任务输出
1 | { |
命令行
在安装turbo包(或克隆一个启动器)之后,您可以开始使用Turborepo的命令行界面(CLI) turbo在您的monorepo中做各种事情。
全局参数
1 | turbo run build --color |
任务参数
1 | turbo run build --cache-dir="./my-cache" |
入门
创建新的monorepo
1 | $ npx create-turbo@latest |
- 创建我们的工程名
- 推荐使用
pnpm构建
turbo 会自动根据我们选择的包管理器为我们创建相对应的项目 然后我们进入项目
1 | pnpm run dev |
添加到现有的monorepo
安装turbo,添加turbo作为monorepo根目录的开发依赖项。
1 | pnpm add turbo -Dw |
创建 turbo.json
在monorepo的根目录中,创建一个名为turbo.json的空文件。这将保存Turborepo的配置。
1 | { |
其他
- 创建管道
- 编辑
.gitignore - 创建
package. json脚本 - 构建
monorepo - 配置远程缓存
按照以上的步骤,你的项目基本已经配置完成。
最后
您现在已经启动并运行Turborepo,但仍有几件事要做:
- 了解
Turborepo缓存的工作原理 - 正确处理环境变量
- 学会用管道编排任务运行
- 高效筛选包任务
- 使用
CI提供商配置Turborepo