打包
文档
核心概念

核心概念

让我们深入了解 Turbopack 的内部机制,找出它为何如此快速。

Turbo 引擎

Turbopack 速度如此之快,因为它构建于一个可重用的 Rust 库之上,该库支持称为 Turbo 引擎的增量计算。以下是它的工作原理

函数级缓存

在使用 Turbo 引擎的程序中,您可以将某些函数标记为“需要记住”。当这些函数被调用时,Turbo 引擎会记住**它们被调用时的参数**以及**它们的返回值**。然后,它会将这些信息保存在内存缓存中。

以下是一个简化的示例,说明这在打包器中可能是什么样子

我们首先对两个文件调用 readFile,分别是 api.tssdk.ts。然后我们 bundle 这些文件,concat 它们,最终得到 fullBundle。所有这些函数调用的结果都会被保存到缓存中,以便以后使用。

假设我们在开发服务器上运行。您在机器上保存了 sdk.ts 文件。Turbopack 会收到文件系统事件,并知道它需要重新计算 readFile("sdk.ts")

由于 sdk.ts 的结果发生了变化,我们需要再次 bundle 它,然后再次进行拼接。

关键的是,api.ts 没有改变。我们从缓存中读取它的结果,并将其传递给 concat。因此,我们通过不再次读取和重新打包它来节省时间。

现在想象一下,在一个真正的打包器中,有成千上万的文件需要读取和执行转换。思维模型是一样的。您可以通过记住函数调用的结果,而不是重新执行之前已经完成的工作,来节省大量的工作。

缓存

Turbo 引擎目前将它的缓存存储在内存中。这意味着缓存将持续到运行它的进程结束 - 这对于开发服务器来说很有效。当您在 Next.js 13+ 中运行 next dev --turbo 时,您将启动一个带有 Turbo 引擎的缓存。当您取消开发服务器时,缓存将被清除。

将来,我们计划将此缓存持久化 - 要么保存到文件系统,要么保存到远程缓存,例如 Turborepo 的缓存。这意味着 Turbopack 可以记住跨运行和机器完成的工作。

它如何帮助我们?

这种方法使 Turbopack 在计算应用程序的增量更新方面非常快。这优化了 Turbopack 处理开发中的更新,意味着您的开发服务器将始终对更改做出快速响应。

将来,持久化缓存将为更快的生产构建打开大门。通过记住跨运行完成的工作,新的生产构建可能只需要重建已更改的文件 - 这可能会带来巨大的时间节省。

按需编译

Turbo 引擎有助于在您的开发服务器上提供极快的更新速度,但还有一个重要的指标需要考虑 - 启动时间。您的开发服务器启动运行的速度越快,您开始工作的时间就越快。

有两种方法可以使进程更快 - 工作更快,或者减少工作量。对于启动开发服务器,减少工作量的办法是只编译启动时需要的代码

页面级编译

2-3 年前的 Next.js 版本在显示开发服务器之前会编译整个应用程序。在Next.js 11 (在新标签页中打开)中,我们开始只编译您请求的页面上的代码

这已经很好了,但还不完美。当您导航到 /users 时,我们会打包所有客户端和服务器模块、动态导入的模块以及引用的 CSS 和图像。这意味着,如果您的页面很大一部分隐藏在视图之外,或者隐藏在选项卡后面,我们仍然会编译它。

请求级编译

Turbopack 足够聪明,可以只编译您请求的代码。这意味着,如果浏览器请求 HTML,我们只编译 HTML - 而不是 HTML 中引用的任何内容。

如果浏览器想要一些 CSS,我们只编译 CSS - 而不编译引用的图像。在 next/dynamic 后面有一个大型图表库吗?在显示图表选项卡之前不会编译它。Turbopack 甚至知道只有在您的 Chrome DevTools 打开时才编译源代码映射

如果我们使用原生 ESM,我们会得到类似的行为。 除了原生 ESM 会产生大量的服务器请求,正如我们在 为什么选择 Turbopack 部分中讨论的那样。 通过请求级编译,我们可以减少请求数量,并使用原生速度来编译它们。 正如您在我们的 基准测试 中看到的那样,这提供了显著的性能改进。