• Pack
  • 文档
  • 核心概念

核心概念

让我们深入 Turbopack 内部了解为什么它如此之快吧。

Turbo 引擎

Turbopack 之所以这么快是因为它建立在一个可复用的 Rust Library 之上,也就是所谓的 Turbo 引擎,使其支持递增式计算。它的运作方式:

函数级缓存

在由 Turbo 驱动的程序里,你可以给特定某些函数打上「记忆」标签。当这些函数被调用时,Turbo 引擎将记住它们调用方式及其返回值。这些状态都将缓存到内存里。

下面是一个工具构建流程经简化的例子

首先,我们对 api.tssdk.ts 两个文件调用 readFile 函数。对这两个进行 构建 后,再将它们 合并 起来,最后得到了 fullBundle 文件。每个调用过的函数的返回值都会在后续进行缓存。

假设我们在运行开发服务器,当你的电脑保存 sdk.ts 时,Turbopack 将接收到系统文件的事件,知道要再次调用 readFile("sdk.ts")

由于 sdk.ts 发生了改变,我们就需要再 构建 一次,然后再重新将文件合并起来。

至关重要的是,api.ts 并未发生改变。我们可以直接从缓存中读取其内容,然后再传递给 concat 函数,这样我们就节省了重复读取和构建文件的时间。

假设一个构建工具有成千上万的文件需要读取和转化,这里心智模型是一样的。你可以像上面一样,通过缓存函数避免重复操作,来节省大量的工作和时间。

缓存

Turbo 引擎目前将缓存放在内存里。这就意味着缓存伴随着整个进程的生命周期,这在开发服务器中是很有用的。当你在 Next v13 运行 next dev --turbo 时,你就开始在用 Turbo 引擎缓存了。当你关闭了开发服务器,缓存就会被清理。

在未来,我们计划将缓存持久化。要么将缓存保存至文件系统,要么就像 Turborepo 那样远程缓存,也就是说 Turbopack 将跨运行、跨机器间支持缓存。

这有什么用?

这个方法令 Turbopack 能够迅速计算出应用的递增式更新,并优化 Turbopack 在开发模式下的更新任务,意味着你的开发服务器可以快速响应文件变化。

未来,持久化缓存将拥抱更快的生产构建。在生产构建中有了函数调用的缓存,将重新构建发生变化的文件,也许能节省大量的时间。

根据请求编译

Turbo 引擎能够为开发服务器提供极快的更新体验,但还有另一个重要的考量 —— 启动时间。开发服务器启动得越快,你就能越快投入工作。

这两种方式可以加快这个过程:提高性能和减少工作量。对于启动开发服务器而言,要减少工作量就得 仅编译需要用到的代码

页面级编译

两三年前的 Next.js 版本会在开发服务器启动前,编译整个应用代码。在 Next.js [11],我们开始只编译 会被请求到的页面代码

这个方式比以往更佳,但不还算不上优秀。当你访问 /users 时,我们会构建所有的客户端和服务端模块、动态导入的模块、CSS、图片文件等其它引用资源。这意味着,如果你的页面有很大一部分内容是不可见的,或者是被标签页隐藏掉了,我们仍然还是会将它们进行编译。

请求级编译

聪明的 Turbopack 会知道 仅编译你请求的代码。如果浏览器请求 HTML,我们只编译 HTML,不会编译 HTML 引用的资源。

如果浏览器想要获取 CSS,我们就只编译 CSS,不会编译其引用的图片文件。假设你在 next/dynamic 后面引入了一个大型图表 Library,只有当标签页可见并且图表显示时,这个 Library 才会被编译。Turbopack 甚至知道只有在你打开了 Chrome DevTools 时,才编译 Source Maps

如果我们使用原生 ESM,我们也能得到差不多的效果,除了它需要向服务器发送大量的请求,这在 Why Turbopack 章节就说过了。拥有了请求级编译,减少请求量的同时,还能有原生语言速度的加持。你可以查看 性能测试 章节,了解其显著的性能提升。