精简你的 JavaScript 代码:掌握打包优化
介绍
过去15年间,JavaScript生态系统发展迅猛,涌现出无数工具来简化开发流程。但这些工具也带来了代价:不断增长的包体积。事实上,HTTP Archive的数据显示,每个页面传输的平均JavaScript文件大小已从2010年的90 KB飙升至2024年的650 KB(来源)。
尽管 JavaScript 的应用越来越广泛,压缩技术也取得了进步,但这种趋势丝毫没有放缓的迹象。随着我们不断添加新功能,挑战依然存在:如何减少 JavaScript 的使用量?
说来也怪,解决方案既简单又困难。简单之处在于项目层面的调整,这些调整可以迅速见效。困难之处在于产生持久的影响,这需要整个社区共同努力,改进打包工具、库和工具本身。
本文重点介绍可应用于您项目的可操作改进方案,内容包括:
- 打包器:优化构建工具以减少输出大小。
- 库:明智地选择和使用外部依赖项。
- 您的项目:缩小软件包大小的实用步骤。
未来的文章将探讨我们可以进行的生态系统层面的改进,但现在,让我们先来探讨这些因素是如何导致软件包臃肿的,以及如何管理它们。
为什么优化 JavaScript 很重要
JavaScript 是现代 Web 交互的引擎,但它并非免费。JavaScript是浏览器需要处理的最耗费计算资源。它通常是决定页面加载速度快慢的瓶颈,因为臃肿的 JavaScript 包会阻塞渲染并降低整体性能。
JavaScript 文件越大,加载、解析、编译和运行所需的时间就越长。这会延迟其他所有操作,例如显示内容或允许用户与页面交互。对于使用高端笔记本电脑和光纤网络的用户来说,这可能只是一个小小的烦恼。但对于使用低端手机或网络不稳定的用户来说,这可能决定他们是否会继续访问你的网站。
减少 JavaScript 包大小的第一步是 tree shaking(或“死代码消除”),大多数打包工具都默认启用此功能。但是,所有打包工具都一样吗?
捆绑商
JavaScript 中的打包技术已经取得了长足的进步——从手动合并代码和使用任务运行器发展到如今功能强大的打包工具。如今,打包工具的性能是开发者关注的焦点,他们优先考虑的是更快的构建速度。然而,构建速度并非一切。生成的包的大小同样重要,因为更小的包意味着更快的用户加载速度。
为了追求更佳的性能,我们已将打包工具的编写语言从 JavaScript 转向 Rust 和 Go 等。这种转变意味着我们需要从头开始编写,因此旧打包工具中的所有特性和优化都必须重新实现。从长远来看,这很可能会带来回报。然而,短期内,这意味着新打包工具缺少一些 JavaScript 打包工具经过多年发展而来的功能,例如优秀的tree shaking和代码压缩。而这些功能恰恰能够帮助我们最大限度地减小打包体积。
基准
当然,光说不练假把式,咱们还是来看看数据吧?
我们来比较八个常用的库,并将它们与七个常用的打包工具一起打包。为了公平起见,我使用了:
- 节点 22.12.0
- 使用 10 次运行(包括 2 次热身运行)计算的平均构建时间
node --run - 配置项会移除所有注释,包括许可证,因为打包工具对注释的处理方式不同。
您可以查看基准测试设置库以获取确切的配置。
已测试的打包工具:
- esbuild(
0.24.0) 带有内置压缩器 - Parcel(
2.13.2) 带有内置压缩器 - Rolldown(
0.15.1) 带rollup-plugin-esbuild最小化器 - Rollup(
4.28.0) 带rollup-plugin-esbuild最小化器 - Rspack(
1.1.5)带有内置压缩器 - Vite(
6.0.3)内置压缩程序 - webpack(
5.97.1) 和 swc 压缩器
请注意,截至撰写本文时,Rolldown 仍处于 alpha 测试阶段,因此它处于劣势,但其结果可能会随着时间的推移而改善。
已测试的库:
chart.jsckeditor5d3handsontableluxonmobxtippy.jszod
这些库的大小和功能各不相同——有些几乎可以像独立应用程序一样运行。
建造速度
我们先来看构建速度,因为开发者似乎非常关心这一点。在将所有这些库打包在一起时,esbuild 以 229 毫秒的构建时间胜出。与最慢的构建时间 7.78 秒相比,速度快了 34 倍以上。
根据这些结果,我们可以将捆绑商分为三类:
- 速度极快™:esbuild、Rolldown、启用缓存的 Parcel(<1 秒,esbuild 不到 250 毫秒)。
- 速度更快:Rspack(约 2.5 秒)。
- 速度慢:Parcel 无缓存、Vite、Rollup、webpack(每个超过 5 秒)。
这些差异非常显著。例如,Rolldown 和 Rspack 的速度分别是其老版本 Rollup 和 webpack 的 7 倍和 3 倍——而且理论上都保持了向后兼容性。切换到这些更新的打包工具可以显著提高大型项目的效率。
输出大小
就输出文件大小而言,差异虽然不像构建时间那样显著,但仍然很重要。
汇总结果
将所有八个库打包在一起时,Rollup 和 Vite 胜出,输出大小为 2081 KiB。与最大的输出大小 2491 KiB 相比,输出大小减少了 19.5% 以上。
输出大小相差 19.5% 是相当大的:在速度较慢的 3G 网络连接下,最小的数据包可能需要大约 5.7 秒才能下载完成,而最大的数据包则接近 7 秒。解析和执行时间也会随着数据包大小的增加而增加,因此实际使用中的差异可能会更加明显。
根据这些结果,我们可以将打包器的输出分为三类:
- 最小:Rollup、Vite、esbuild、Rolldown 和 Parcel(约 2081–2162 KiB)。
- 好的:webpack(~2317 KiB)。
- 大:Rspack(约 2491 KiB)。
使用这套特定的库时,webpack 和 Rspack 生成的包体积最大。但这其中是有原因的。在后面的章节中,我们将更详细地探讨这个问题,并介绍一种优化方法,该方法有助于减小 webpack 和 Rspack 的包体积,使 webpack 的包体积与 Rollup 和 Vite 的包体积相当。
个人图书馆
汇总结果并不能反映全貌,因为你不太可能在项目中使用上面列出的所有库。更有意思的是这些打包工具如何处理各个库。
对于像这样的库chart.js,打包工具的选择会对输出文件大小产生显著影响,差异最高可达 70%。这凸显了针对特定依赖项测试打包工具的重要性。在大多数其他情况下,差异要小得多,最高不超过 22%。
此外,虽然 webpack 的总体排名居中,但在 8 个测试案例中,有 4 个案例的表现最佳。然而,由于它在打包handsontable和chart.js处理文件时表现较差,最终排名也因此受到影响。这意味着,webpack 是否是一个不错的选择,取决于你使用的库。
另一方面,Rspack 的表现则差强人意。在 8 个案例中,它有 5 个案例表现最差,生成的软件包比其他打包工具要大得多。
捆包尺寸与输出速度
如所示,有些打包工具可能会生成比其他工具更大的构建文件,并且结果也可能取决于所使用的库。
为项目选择打包工具或进行迁移时,不要只比较构建时间,还要比较生成的打包文件大小。你可能会发现,更快的构建速度是以更大的打包文件为代价的。此外,你还应该使用你常用的库进行测试,确保它在你的特定场景和配置下能够正常工作。
例如,Angular 从 webpack 切换到 esbuild 后,一些开发者反映,一个空的 Angular 应用的大小增加了大约 20 KB。这完美地凸显了构建速度与打包大小之间的权衡。
这并不是说你不应该关注构建速度,因为它对开发者的效率和满意度至关重要。此外,持续集成构建时间和代码合并所需时间之间也存在关联。
选择打包工具时,首先要查看它提供的功能。然后,在构建速度和打包体积之间找到平衡点。选择能够在您可接受的时间内生成尽可能小打包文件的打包工具。
测试项目中几个具有代表性的库。如果您的依赖项构成了代码库的大部分,那么这些基准测试结果的差异可以很好地预测您的实际情况。
图书馆
接下来要介绍的是外部库,它们通常占据 JavaScript 打包文件的大部分。在我参与开发的许多(如果不是大多数)应用程序中,它们都占了打包文件大小的大部分。因此,明智地选择(和使用)它们至关重要。
黄金,但古老
我们很多人为了使用某个特定功能而安装了诸如 `library` lodash、axios`library` 或 ` library` 之类的库moment,导致应用程序臃肿不堪。这些库固然强大且具有重要的历史意义,但随着它们越来越流行,更轻量级的替代方案应运而生,并且它们的一些特性也被集成到了编程语言本身中。
我们可以利用这一点。我可以列举这些库的原生 API 或更新、更小的替代方案,但已经有很多文章介绍过这些内容了。而且还有许多其他库,不可能一一介绍。
因此,我只会给你一些建议,比如检查一下你使用的库,看看是否可以移除它们,或者用原生 API 或更小的替代方案替换它们。“你可能不需要*”网站是一个很好的入门资源。
打包工具特定优化
不同的打包工具支持不同的功能,这也会对输出文件的大小产生显著影响。我们在最初的基准测试中已经看到了这一点,webpack 和 Rspack 生成的包最大。但这背后是有原因的。
该库内部handsontable使用moment某种方式处理日期,这种方式通过动态require()调用来加载本地化文件。由于动态require()调用依赖于运行时值,webpack 和 Rspack 会将所有本地化文件打包,以防万一需要。根据您的配置,这可能正是您所需要的。
大多数情况下,您不需要所有语言环境,只需要您选择的那些即可。因此,我们可以配置 webpack 和 Rspack 不打包它们,从而显著减小打包后的文件大小。
// webpack.config.mjs / rspack.config.mjs
export default {
module: {
noParse: [ /moment.js/ ]
}
};
这一小小的改动使两个包的大小都减少了handsontable近 20%,并使包含所有依赖项的 webpack 包与 Rollup 和 Vite 的包大小相当。
优化的安装路径
大多数库默认情况下并未针对体积进行优化,但有些库提供了特殊的安装路径或部分构建选项。即使在我们测试的库中,也有一些库(例如 `<library>`、`<library>`chart.js和handsontable` ckeditor5<library>`)提供了仅包含所需部分来减小库体积的方法。我们以 `<library>`ckeditor5为例来看一下。
默认安装路径会导致软件包大小在 666 到 786 KiB 之间。但是,如果使用优化后的安装路径,软件包大小会降至 603 到 659 KiB 之间。根据打包工具的不同,这相当于减少了 7% 到 23% 的软件包大小。
重复依赖项
另一个需要注意的地方是重复依赖项。这在 JavaScript 应用中是一个相当常见的问题。例如,Bluesky 的嵌入式组件就有两个版本的zod验证库。移除重复的依赖项后,包大小减少了约 9%。
这个问题通常不是因为你拉取了同一个库的两个不同版本,而是因为你和某个外部库都依赖于同一个库,但版本不同。这种情况通常可以通过更新你所依赖的库来解决。
您的项目
考虑到以上所有因素,我们终于可以着手处理最后一块拼图——你的项目了。以下是一些可以缩小项目包大小并提升性能的方法。
检查您的包裹
第一步是提高可见性。如果不了解软件包内部的内容,缩小软件包大小就只能靠猜测。为此,您可以使用我开发的名为Sonda的软件包分析和可视化工具。它适用于上述大多数打包工具(Parcel 除外),并能准确显示构成软件包的各个文件的大小。
您可以先将其安装到您的项目中,然后直观地检查软件包的各个部分。
一旦你充分了解了捆绑包内部的内容,并确定了可以优化的部分,你就可以点击图表图块查看:
- 压缩前后的文件大小
- 导入所选文件的文件列表
- 甚至可以检查捆绑包中包含的部分源代码。
Sonda 还会提醒您注意重复的依赖项,以便您可以快速识别并解决问题的根源。
理想情况下,您不仅应该进行一次性检查,还应该在持续集成 (CI) 流水线中设置持续监控。跟踪一段时间内的变更,尤其是在大型项目中,可以帮助您防止小的变更随着时间的推移而累积成巨大的代码膨胀。
移除或优化外部库
最快的代码就是你不发布的代码。尽可能这样做:
- 移除可以用原生 API 替代的库。
- 用更小巧的库替换笨重的库。
- 如果库支持,请使用优化的安装路径。
使用代码分割
如果无法移除应用程序的某些部分,可以尝试代码分割。代码分割允许你将应用程序的某些部分延迟加载,直到需要时才加载,从而缩短初始加载时间。
使用动态方式import()按需加载模块。例如,如果某个特定功能在用户点击按钮之前不需要,则延迟加载该功能,直到用户点击按钮时才加载。
现代前端框架开箱即用地支持延迟加载,使得将代码拆分集成到工作流程中比以往任何时候都更加容易。
遵循最佳实践
这是一条通用建议,但值得重申。请遵循最佳实践,例如:
- 尽量使用最新版本
target,这样可以避免代码被不必要地转译或添加 polyfill。有些 polyfill 会在现代浏览器中添加大量完全不需要的代码,但许多环境仍然默认添加它们。您还可以设置提醒,每年更新一次target。 - 定期更新依赖项,因为新版本通常会更小巧或速度更快。这还可以避免处理安全漏洞或重复依赖项的问题。
- 评估您已有的或正在考虑添加的每个依赖项。如果无法证明其体积合理,请不要添加,或寻找体积更小的替代方案。
加入生态系统绩效 (e18e) 社区
如果您对提升网络速度或学习新知识感兴趣,不妨考虑加入生态系统性能社区。我们主要关注以下三个领域:
- 清理——通过删除冗余依赖项或用现代替代方案替换它们来改进软件包。
- 加速——提高常用软件包的性能。
- 升级——构建现代化的替代方案,取代过时的软件包。
结论
我希望这篇文章能说明,你可以用更少的代码实现相同的功能。如果代码管理不善,包的大小可能会失控增长,但即使是微小的改动也能显著提升性能。
立即行动:分析你的代码包、测试新工具或替换重量级库。其影响将令你惊喜。
希望您喜欢这篇文章。如果您有任何问题或评论,或者想了解更多特定主题,请在下方评论区留言。如果您想了解更多关于 JavaScript 性能、打包和 tree-shaking 的内容,可以关注我的博客或BlueSky,并加入 e18e 社区。
文章来源:https://dev.to/filipsobol/downsize-your-javascript-mastering-bundler-optimizations-2485






