Parcel 2 beta 3

Parcel 团队很高兴发布 Parcel 2 beta 3!这个版本包括对我们的 JavaScript 编译器的彻底重写,使用 Rust,将整体构建性能提高了最多 10 倍。 除此之外,这篇文章还将介绍自上次更新以来我们对 Parcel 做出的一些改进,以及我们通往稳定版 Parcel 2 发布的路线图。

用 Rust 编写的 10 倍快速 JavaScript 编译器 🚀

#

在过去的几个月里,我们一直在用 Rust 重写我们的 JavaScript 编译器!Parcel 的 JavaScript 编译器负责检测代码中的依赖(如 import 语句和 new Worker() 调用)、内联 process.env 变量和其他 Node 全局变量,以及执行作用域提升。

此外,Parcel 会自动为你配置的 browserslist 目标转译源代码,包括非标准语法如 JSX 和 TypeScript,以及开发特性如 React Fast Refresh。

以前,所有这些都是在 Babel AST 之上用 JavaScript 实现的。尽管我们在优化方面取得了很大进展,但 JavaScript 编译仍然是 Parcel 中最慢的部分。特别是,在线程之间发送大型 JavaScript AST 的序列化非常慢,这些大对象给 JavaScript 垃圾收集器带来了很大压力。另外,JavaScript 代码必须在每次运行时由引擎编译,这意味着启动速度很慢。虽然改进算法复杂性无论使用什么语言都会带来性能改进,但这只能走到一定程度。

Parcel 的新 JavaScript 转换器是基于 SWC 编译器用 Rust 编写的。SWC 提供 JavaScript 解析和代码生成,以及构建超快 AST 转换的坚实基础。Rust 提供可预测的性能、即时启动时间,以及优化到硬件级别的能力。

此外,作用域提升链接器现在仅在字符串上操作,而不是 AST,这也极大地提高了性能,因为我们避免了序列化和反序列化这些大对象。这还允许代码生成可以跨所有文件并行,而不是一次性对整个包进行。

总体上,这将构建性能提高了最多 10 倍!

在 ESBuild 基准测试中,Parcel 现在在没有 Terser 的情况下快约 10 倍,启用压缩时快约 3 倍。

显示先前 beta 版本与 Rust 重写版本性能的图表。没有 terser 时,先前的 beta 版本用时 34 秒,Rust 重写版本用时 3.2 秒。使用 terser 时,先前的 beta 版本用时 56 秒,Rust 重写版本用时 18 秒。

转译性能

#

此外,当设置了 browserslist 时,SWC 默认替换 Babel 进行转译,同时还用于编译 JSX 和 TypeScript,以及 React Fast Refresh。SWC 比 Babel 快 20 倍,因此这一变化应该也会显著提高整体性能。

虽然 SWC 是默认选项,但请放心,Babel 仍然得到支持。如果你的项目中有自定义的 Babel 配置,它仍将自动使用。这意味着自定义 Babel 插件(例如 CSS-in-JS 转换、Babel 宏等)将继续开箱即用。作用域提升、依赖收集以及之前内置于 Parcel 的所有内容现在都将在 Rust 中进行,但这应该是完全透明的。

然而,这确实为进一步提高构建性能打开了可能性。现在,你可以从 .babelrc 中删除 @babel/preset-env@babel/preset-react@babel/preset-typescript,它们将自动由 SWC 处理。这可以显著提高你的构建性能。如果你有额外的自定义 Babel 插件,可以将它们保留在 Babel 配置中。如果没有,你可以完全删除 Babel 配置。我们可能会在将来添加警告以帮助进行此迁移。

作用域提升改进

#

除了性能,我们在旧的作用域提升实现中遇到了一些问题,这促使了重写。特别是,包括 Parcel 在内的几个 JavaScript 打包器在使用作用域提升和代码分割时存在与执行顺序相关的 bug

作用域提升是将多个 JavaScript 模块组合到单个作用域中的过程。这使得死代码消除(又称树摇)更加有效,并通过使跨模块引用静态而不是动态属性查找来改善运行时性能。

当将多个模块提升到同一作用域而不是将每个模块包装在单独的函数中时,很难确保在跨包引用时这些模块始终按正确顺序执行。

此外,有时小模块会在多个包之间重复,以避免生成许多非常小的输出文件并增加加载页面所需的 HTTP 请求数。以前,这可能导致模块执行多次,这可能会破坏许多事情,包括副作用(例如修改 DOM)和单例模式。

为了解决这些问题,有必要从根本上重新思考我们的作用域提升编译器的工作方式。结果是一种混合方法,在可能的情况下进行作用域提升,但在需要时回退到使用全局模块注册表包装模块的 CJS 风格函数。这确保了跨包引用的模块按正确顺序执行,并且重复的模块只执行一次。

如果你想更详细地了解我们的作用域提升实现,请查看设计文档,它详细介绍了所有这些主题。

动态导入树摇

#

与我们的作用域提升实现相关的另一个特性是支持动态 import() 的树摇。Parcel 可以检测动态导入的哪些属性被访问,并排除未使用的已解析模块的导出。这适用于 Promise 链、async/await、解构和静态对象属性访问。如果以非静态方式访问任何内容,例如计算属性,则将包含所有导出。

动态导入树摇示例

CSS 模块树摇

#

我们现在还支持 CSS 模块的树摇。当你在 JavaScript 中导入 CSS 模块时,Parcel 会跟踪使用的类,并自动从编译后的 CSS 文件中排除未使用的选择器。此外,类名现在会自动内联到编译后的 JavaScript 中,而不是存储在大型对象映射中。这应该有助于减小 CSS 和 JS 输出的包大小!

CSS 模块树摇示例

懒惰的开发构建

#

在开发中,等待整个应用构建完成后开发服务器才启动可能会很烦人。对于处理大型多页面应用尤其如此。如果你只在处理一个功能,为什么还要等待其他所有页面构建,除非你实际导航到它们?

Parcel 现在在使用开发服务器时支持 --lazy CLI 标志。启用后,Parcel 仅构建按需请求的文件,这可以显著减少开发构建时间。服务器立即启动,当你第一次导航到页面时,Parcel 仅构建该页面所需的文件。当你导航到另一个页面时,该页面将按需构建。如果你返回到之前构建的页面,它会立即加载。

这不仅适用于完全独立的页面,还适用于动态 import()。因此,如果你有一个带有动态加载功能的页面,该功能将不会被构建,直到它被激活。Parcel 足够智能,可以立即构建所请求文件的所有依赖,而不需要等待它们也被请求。如果你有一些与 JavaScript 一起的 CSS,或一些引用的图像,它们都将一起构建 - 没有网络瀑布!

权衡之处,当然是首次加载页面和动态导入可能稍微慢一些。但是使用 Parcel 的磁盘缓存,这应该只发生一次。即使你重新启动 Parcel,也不需要重新构建未更改的文件。

缓存可靠性

#

说到缓存,我们一直在努力的另一个领域是缓存可靠性。我们希望用户能信任缓存,因此需要确保所有可能使缓存失效的因素都确实使缓存失效。这比听起来要困难得多。现代 JavaScript 构建工具有很多需要跟踪的输入 - 磁盘上的文件、配置、环境变量、用于编译代码的开发依赖等等。当这些任何一个发生变化时,Parcel 需要使缓存失效并重新计算输出。

Parcel 在图中跟踪所有这些输入。我们的文件监视器监视磁盘上的更改,并使与已更改文件相连的任何请求失效。重新启动 Parcel 时也会发生这种情况。我们的监视器与操作系统级 API 集成,快速确定自上次运行 Parcel 以来哪些文件发生了变化,这意味着重新启动 Parcel 几乎和使用监视模式一样快!

开发依赖热模块替换(HMR)

#

所有这些缓存失效工作的一个附带好处是监视模式也受益。我们实现的一个很酷的特性是 Parcel 插件的 HMR,以及其他构建依赖。当你更改插件的源代码时,我们就地重新加载插件并重新构建它之前编译的任何文件。这使得处理插件并立即看到更改变得非常快 - 无需重新启动 Parcel。

它还适用于 Babel 插件、PostCSS 插件和构建中涉及的任何其他开发依赖。你甚至可以编辑 node_modules 中的插件,Parcel 将自动重新编译。当你需要调试构建管道中的某些内容,或使用 patch-package 等工具时,这很有用。

更少的依赖

#

Parcel 开箱即用地支持许多不同的语言和工具,这使得入门变得非常容易。但这的一个缺点是安装 Parcel 会包含许多你可能不会使用的依赖。这不仅占用磁盘空间和网络带宽,还增加了你需要维护和审核的依赖数量。

我们希望在保持使用非常简单的同时减少 Parcel 的依赖。为此,我们现在默认只安装基本插件,并按需自动将其他插件安装到你的项目中。

默认情况下,我们包含对标准 Web 语言(如 HTML、CSS 和 JavaScript)的支持。但是,一旦你包含 SASS 文件、Vue、Elm、CoffeeScript 或 Parcel 识别的任何其他文件类型,我们将自动将必要的插件及其所有对等依赖安装到你的项目中。这使 Parcel 保持超级易用(零配置),同时还减少了项目中的依赖数量。

路线图

#

Parcel 2 已经开发了几年,我们并没有始终很好地向社区更新我们在通往稳定版本路线图上的进展。因此,本节是我们进展的更新。

Beta 3 很可能是第一个发布候选版本之前的最后一个测试版,我们希望大约一个月后发布。对于第一个 rc,我们正在处理以下项目:

在 rc 之后,公共 API 将被冻结,我们将专注于 bug 修复和文档。这应该需要大约一个月。准备就绪后,我们将发布 2.0!

此时,Parcel 2.0 中打包器、运行时和验证器的插件 API 很可能会被标记为实验性。这意味着这些特性不会遵循语义化版本,我们将在初始稳定的 Parcel 2 发布后继续对其进行迭代。此外,我们还计划在 2.0 之后推出许多其他特性和改进,包括进一步的性能优化。

感谢!

#

一如既往,感谢你尝试我们的测试版并在 GitHub 上给我们反馈。你还可以向我们的开放集体捐款,这有助于支持我们的贡献者。