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