译:Vercel 为什么要在已有 Vite 时仍然创建 Turbopack?

原文:https://github.com/vercel/next.js/issues/48748#issuecomment-2199941311
作者:timneutkens
译者:ChatGPT 4 Turbo

@timneutkens 如果可以回答我的问题,#48748 (comment),这是一个真诚的问题,我真的很想知道为什么 vercel 想要重新发明轮子,而 JS 社区已经有了一个像 Vite 这样强大而快速的工具,它被大多数元框架所使用?

我会尽量保持简短,因为我可以写/讲这个话题几个小时 😄

几年前,在 Vite 获得较多采用之前,我们开始看到越来越庞大的 web 应用程序基于 Next.js 构建,包括 100+ 开发者的企业级团队的采用。这些代码库增长到数以万计的自定义组件,再加上从 npm 导入包。简而言之:即便 webpack,在当时我们正在使用的(如果你没有选择加入 Turbopack 的话现在也仍然在使用),实际上相当快,但对于这些不断增长的代码库大小来说还是不够快。

我们还看到了一个趋势,即通用应用程序变得更加依赖编译,这在很大程度上是由于组件库/图标库的兴起。正如你在这个讨论中看到的,如今一个超小的应用程序因为使用了发布的设计系统和图标库而最终编译 20K 或更多模块是很常见的。

我们看到的问题是,即使我们将 webpack 优化到最大,仍然存在一个处理模块数量的上限,因为如果你有 20,000 个模块,即使每个模块花费 1 毫秒,如果你不能并行处理,这也会加起来变成 20 秒。

除此之外,我们不只是运行 1 个 webpack 编译器,我们运行了 3 个。一个用于服务器,一个用于浏览器,一个用于边缘运行时。这造成了复杂性,因为这些独立的编译器必须协调,因为它们没有共享的模块图。

差不多同时,我们也开始探索 React 服务端组件、应用路由,以及总体上我们希望 Next.js 开发在未来 5-10 年内的样子。其中的一个主要话题围绕着可以从服务器->客户端->服务器->客户端的代码,简而言之,如果你熟悉,服务端操作,特别是服务端操作可以返回持有附加客户端组件的 JSX。为了使其工作,我们发现拥有一个单一统一模块图,可以在同一个打包器/编译器中持有服务器、客户端和边缘代码会非常有益。这是像 Parcel 这样的打包器已经探索了相当长时间的东西。

在我们评估了所有现有的解决方案时,发现每个解决方案都有其权衡。我不打算“批评别人”,因为这些权衡都是有道理的,它们只是对于像 Next.js 这样的框架,特别是未来的 Next.js 来说并不合适(如果我没记错的话,这是在大约 2020 年的时候)。

总的来说,让我们稍微谈谈目标,其中一些目标对你作为用户有益,一些有益于维护:

  • 更快的 HMR
    • Webpack 在模块图中的模块数量上有一个性能限制。一旦你达到 30K 模块,每次代码更改至少需要大约 1 秒的处理时间,不论你是否只是做了一个小的 css 更改
  • 更快的初始化路由编译
    • Webpack 在处理 20-30K 模块时会一致地需要 15-30 秒,因为它不能在 CPU 间并行处理
  • 无破坏性改变
    • 我们希望给现有的应用程序带来所有这些改进。作为其中的一部分,需要增加很多 Next.js 特有的编译器特性,比如 next/font
    • 每个打包器都有自己的行为/工具。例如,即使是将 Turbopack 的 CSS 解析器切换到 Lightning CSS 也已被证明是个问题,因为尝试 Turbopack 的人报告说与 webpack 相比存在行为差异,使用现成的现有打包器意味着会有数百个这样的小差异。在这种情况下,我们能够改变解析处理方式以密切匹配 webpack 的行为。
  • 扩展至最大的 TS/JS 代码库
    • 正如上面所说,我们正在看到越来越大的代码库,为了优化这些,需要不同的架构。我认为我们着陆的架构与其他打包器中最接近的是 Parcel。
    • 对于小型代码库,如果你以相同的方式设置它们,你不会看到与其他打包器在初始编译时间/ hmr 时间上的大差异
  • 持久化缓存
    • Turbopack 有一个广泛的缓存机制,可以与 Facebook 的 Metro 打包器(用于 react-native 和 instagram.com)相比较,它将能够持久地缓存之前完成的工作,这样当你重启开发服务器时,只需要恢复你上一次会话的缓存,这目前正在开发中。
    • 这个缓存也适用于生产构建,当你进行后续构建时,它只需要重新编译你改变的部分,显著加快生产构建速度。
  • 生产构建与开发密切匹配
    • 当前在 Next.js 和其他打包工具里,开发环境和生产环境之间存在差异。我们希望最小化这些差异。
  • 超越当前打包工具的生产优化
    • 我们一直在研究高级的摇树功能,这些功能受到 Closure Compiler 的启发,允许在导入/导出级别而非模块级别进行代码分割。当前的打包工具是在模块级别操作的。
  • 编译器的健壮性和编译时间的减少
    • 目前,由于需要协调服务器/客户端/边缘 webpack 编译器之间的工作,有时因为多个 webpack 实例之间的协调,编译会需要更长时间。其中一个主要目标一直是减少实现的复杂性,并让打包工具在一次编译过程中输出所有需要的文件。
  • (以后)支持 Next.js 的打包工具
    • 例如,大幅改进的打包分析工具,能够识别布局/页面/路由
  • (以后)支持 Next.js / RSC 的打包优化
    • 例如,优化客户端组件打包方式,使其尽可能高效地加载
  • 维护者的完整可观察性
    • Next.js 有很多使用场景,随之而来的是大量的错误报告/功能请求。其中一种特别难以调查的错误报告与性能下降(这个问题就是一个很好的例子)和内存使用相关(“Next.js 正在泄露内存”的报告)。这类问题因为需要深入了解报告者的性能分析/内存转储,而难以调查,因为他们一般不愿意分享可运行的代码(同样这个问题就是一个很好的例子)。
    • 这就是为什么自建工具对我们来说很有益处的一个大原因,它允许我们调查报告的问题,而不需要访问你的代码库。如果我们使用的是其他打包工具,我们不得不说“运气不好,这是你的问题,现在试着向该打包工具的 GitHub 仓库报告”,这不是我们想要做的事情,我们也没有对 webpack 这样做过。

个人而言,看到 Vite 在生态系统中表现良好我感到很高兴。它们也在从其他打包工具中吸取经验。如果你关注一下它们最近与 Rolldown 的合作,你会发现不少相似之处,比如它们回归到 bundling 而非 “unbundling” 以提升编译性能。

我猜写这篇文章还是花了我不想投入的更多时间,但我希望它是有帮助的!

简而言之:其他打包工具都很棒,但它们不适合像 Next.js 这样的框架,我们想将这些改进带给现有用户,为了做到这一点,我们不得不构建一个新的打包工具,这个打包工具吸收了之前尝试过的许多不同方法的经验。