压缩 JavaScript

压缩 JavaScript 并关注压缩后的块大小以实现最佳性能。过高的 JavaScript 打包粒度有助于消除重复项和缓存,但可能在 50-100 块范围内受到较差的压缩和加载影响(由于浏览器进程、缓存检查等)。最终,选择最适合您的压缩策略。

JavaScript 是导致页面大小增大的第二大因素,也是互联网上仅次于图像的请求量第二大的网络资源。我们使用一些模式来减少 JavaScript 的传输、加载和执行时间,以提高网站的性能。压缩可以帮助减少通过网络传输脚本所需的时间。

您可以将压缩与其他技术(如缩小、代码分割、打包、缓存和延迟加载)结合使用,以减少大量 JavaScript 对性能的影响。然而,这些技术的目标有时可能会发生冲突。本节将探讨JavaScript压缩技术,并讨论在决定代码分割和压缩策略时应考虑的问题。

  • Gzip 和 Brotli 是压缩 JavaScript 和 受到现代浏览器的广泛支持。
  • Brotli 在相似的压缩下提供更好的压缩比 水平。
  • Next.js默认提供 Gzip 压缩,但建议在 Nginx 等 HTTP 代理上启用它。
  • 如果您使用 Webpack 打包代码,则可以使用 CompressionPlugin 进行 Gzip 压缩,或者使用 BrotliWebpackPlugin 进行 Brotli 压缩。
  • Oyo 减少了 15-20%,Wix 在切换到 Brotli 压缩而不是 Gzip 后,文件大小减少了 21-25%。
  • compress(a + b) <= compress(a) + compress(b) - 一个大 bundle 包比多个较小的bundle 包有更好的压缩效果。这导致粒度的权衡,其中去重和缓存与浏览器性能和压缩存在矛盾。粒度分块可以帮助处理这种权衡。

HTTP 压缩

压缩减小了文档和文件的大小,因此它们占用的磁盘空间比原始文件少。较小的文档消耗的带宽较低,并且可以通过网络快速传输。HTTP 压缩使用这个简单的概念来压缩网站内容、减少页面权重、降低带宽要求并提高性能。

HTTP 数据压缩可以以不同的方式分类。其中之一是有损与无损。

有损压缩意味着压缩-解压缩循环会导致文档发生细微变化,同时保留其可用性。 对于最终用户来说,这种变化通常是察觉不到的。 最常见的有损压缩示例是用于图像的JPEG压缩。

无损压缩后,经过压缩和随后解压的数据将与原始数据完全匹配。PNG 图像就是无损压缩的一个例子。无损压缩与文本传输有关,应应用于基于文本的格式,如 HTML、CSS 和 JavaScript。

由于您希望在浏览器上使用所有有效的 JS 代码,因此您应该对 JavaScript 代码使用无损压缩算法。在压缩 JS 之前,缩小有助于消除不必要的语法,并将其简化为仅执行所需的代码。

缩小

若要减小负载大小,可以在压缩之前缩小 JavaScript。缩小通过删除空格和任何不必要的代码来创建更小但完全有效的代码文件,从而补充压缩。在编写代码时,我们使用换行符、缩进、空格、命名良好的变量和注释来提高代码的可读性和可维护性。但是,这些元素会影响整体 JavaScript 大小,并且不是在浏览器上执行所必需的。缩小将 JavaScript 代码减少到成功执行所需的最小值。

缩小是 JS 和 CSS 优化的标准做法。JavaScript 库开发人员通常会为生产部署提供其文件的缩小版本,通常用 min.js 名称扩展名表示。(例如,jquery.js 和 jquery.min.js

有多种工具可用于缩小 HTML、CSS 和 JS 资源。Terser 是 ES6+ 上流行的 JavaScript 压缩工具,Webpack v4 默认包含该库的插件,用于创建缩小的构建文件。您还可以将 TerserWebpackPlugin 与旧版本的 Webpack 一起使用,或者将 Terser 用作没有模块打包器的 CLI 工具。

静态压缩与动态压缩

最小化有助于显著减少文件大小,但压缩JS可以提供更显著的收益。你可以在服务器端以两种方式实现压缩。

静态压缩:您可以使用静态压缩来预压缩资源,并在生成过程中提前保存它们。在这种情况下,您可以使用更高的压缩级别来缩短代码的下载时间。较长的构建时间不会影响网站性能。最好对不经常更改的文件使用静态压缩。

动态压缩:通过此过程,当浏览器请求资源时,会即时进行压缩。动态压缩更易于实现,但仅限于使用较低的压缩级别。更高的压缩级别将需要更多的时间,并且您将失去从较小的内容大小中获得的优势。如果对频繁更改或应用程序生成的内容使用动态压缩,这将有所帮助。

您可以使用静态或动态压缩,具体取决于应用程序内容的类型。您可以使用常用的压缩算法启用静态和动态压缩,但每种情况下建议的压缩级别都不同。让我们看一下压缩算法来更好地理解这一点。

压缩算法

Gzip 和 Brotli 是当今用于压缩 HTTP 数据的两种最常用算法

Gzip

Gzip 压缩格式已经存在了近 30 年,是一种基于 Deflate 算法的无损算法。deflate 算法本身结合使用 LZ77 算法和霍夫曼编码对输入数据流中的数据块进行编码。

LZ77 算法识别重复的字符串,并将它们替换为反向引用,反向引用是指向它之前出现的位置的指针,后跟字符串的长度。随后,霍夫曼编码识别常用的引用,并用具有较短位序列的引用替换它们。较长的位序列用于表示不常用的引用。

Image Courtesy: https://www.youtube.com/watch?v=whGwm0Lky2s&t=851s
图片提供:https://www.youtube.com/watch?v=whGwm0Lky2s&t=851s

所有主流浏览器都支持 Gzip。Zopfli 压缩算法是 Deflate/Gzip 的较慢但改进的版本,可生成较小的 GZip 兼容文件。它最适合静态压缩,可以提供更显着的增益。

Brotli

2015 年,Google 推出了 Brotli 算法和 Brotli 压缩数据格式。与 GZip 一样,Brotli 也是一种基于 LZ77 算法和霍夫曼编码的无损算法。此外,它还使用二阶上下文建模以相似的速度产生更密集的压缩。上下文建模是一项功能,它允许在同一块中为同一字母表使用多个霍夫曼树。Brotli 还支持更大的反向引用窗口大小,并具有静态字典。这些功能有助于提高其作为压缩算法的效率。
如今,所有主要服务器和浏览器都支持Brotli,并且越来越受欢迎。托管服务提供商和中间件(包括 NetlifyAWS 和 Vercel)也支持并轻松启用它。

拥有庞大用户群的网站,如OYOWix,在用Brotli取代Gzip后,其性能得到了显着提高。

下表显示了不同压缩级别下 Brotli 和 Gzip 压缩比和速度的基准比较。

此外,以下是 Chrome 研究对使用 Gzip 和 Brotli 压缩 JS 的一些见解

  • Gzip 9 具有最佳的压缩率和良好的压缩速度,您应该考虑在其他级别的 Gzip 之前使用它。
  • 对于 Brotli,请考虑 6-11 级。否则,我们可以用 Gzip 更快地实现类似的压缩率。
  • 在所有尺寸范围内,Brotli 9-11 的性能都比 Gzip 好得多,但它的速度相当慢。
  •  bundle 包越大,压缩率和速度就越好。
  • 对于所有包大小,算法之间的关系都是相似的(例如,对于每个包大小,Brotli 7 都比 Gzip 9 好,对于所有大小范围,Gzip 9 都比 Brotli 5 快)。

现在让我们看一下服务器和浏览器之间关于所选压缩格式的通信。

启用压缩

您可以在构建过程中启用静态压缩。如果您使用 Webpack 打包代码,则可以使用 CompressionPlugin 进行 Gzip 压缩,或者使用 BrotliWebpackPlugin 进行 Brotli 压缩。该插件可以包含在 Webpack 配置文件中,如下所示。

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin(),
  ],
};

 Next.js默认提供 Gzip 压缩,但建议在 Nginx 等 HTTP 代理上启用它。Gzip 和 Brotli 在 Vercel 平台上的代理级别都受支持。

您可以在支持不同压缩算法的服务器(包括Node.js)上启用动态无损压缩。浏览器通过请求中的 Accept-Encoding HTTP 标头传达它支持的压缩算法。例如,Accept-Encoding: gzip, br.

这表明浏览器支持 Gzip 和 Brotli。您可以按照特定服务器类型的说明在服务器上启用不同类型的压缩。例如,您可以在此处找到在 Apache 服务器上启用 Brotli 的说明。Express 是 Node 的流行 Web 框架,它提供了一个压缩中间件库。使用它来压缩请求的任何资源。

审核压缩

可以在 Chrome -> DevTools -> network -> Headers 中检查服务器是否压缩了下载的脚本或文本。DevTools 显示响应中使用的内容编码,如下所示。

lighthouse 报告包括对“启用文本压缩”的性能审计,该审计检查在未将内容编码标头设置为“br”、“gzip”或“deflate”的情况下接收的基于文本的资源类型。Lighthouse 使用 Gzip 来计算资源的潜在节省。

 图片提供:​​​​​​https://web.dev/uses-text-compression/#how-to-enable-text-compression-on-your-server

JavaScript 压缩和加载粒度

 要完全掌握 JavaScript 压缩的效果,您还必须考虑 JavaScript 优化的其他方面,例如基于路由的拆分代码拆分和打包。

具有大量 JavaScript 代码的现代 Web 应用程序通常使用不同的代码拆分和打包技术来有效地加载代码。应用使用逻辑边界来拆分代码,例如单页应用程序的路由级别拆分,或在交互或视口可见性上增量提供 JavaScript。您可以配置打包程序以识别这些边界。

在继续讨论代码拆分和打包如何影响压缩之前,让我们介绍一些与代码拆分和打包相关的基本定义。

打包术语

模块:模块是离散的功能块,旨在提供可靠的抽象和封装。有关详细信息,请参阅模块模式

Bundle:包含源文件的最终版本并已在捆绑程序中完成加载和编译过程的不同模块组。

Bundle splitting:  打包程序用于将应用程序拆分为多个 bundle 包的过程,以便可以独立隔离、发布、下载或缓存每个 bundle 包。

Chunk: 在Webpack术语中,chunk是打包和代码分割过程的最终输出。Webpack可以根据 entry  配置、SplitChunksPlugin或动态导入来将捆绑包分割成多个chunk。

如果模块包含在源文件中,则代码或打包拆分后构建过程的最终输出称为chunk。请注意,源文件和 chunk 可能相互依赖。

图片提供:https://www.youtube.com/watch?v=ImjzA7EMI6I&list=PLyspMSh4XhLP-mqulUMcaqTbLo-ZJxSX5&index=29

 JavaScript 的输出大小是指 JavaScript 打包器或编译器优化后的块大小或原始大小。大型 JS 应用程序可以解构为可独立加载的 JavaScript 文件块。加载粒度是指输出块的数量,块的数量越多,每个块的大小越小,粒度越高。

某些块比其他块更重要,因为它们的加载频率更高,或者是更有影响力的代码路径的一部分(例如,加载“checkout”小部件)。要知道哪些块最重要,需要应用知识,但可以肯定的是,“基本”块始终是必不可少的。

页面所需的区块的每个字节都需要由用户设备下载并解析/执行。这是直接影响应用程序性能的代码。由于块是最终将下载的代码,因此压缩块可以带来更好的下载速度。

在此背景下,让我们讨论加载粒度和压缩之间的相互作用。

粒度权衡

1、提高下载速度:如前面的部分所示,可以使用压缩来提高下载速度。但是,与使用相同代码压缩多个小块相比,压缩一个大块将产生更好的结果或更小的文件大小。

compress(a + b) <= compress(a) + compress(b)

有限的本地数据表明,较小的区块损失为 5% 到 10%。在未捆绑块的极端情况下,大小增加了 20%。额外的 IPC、I/O 和处理成本附加到每个块,如果块较大,则共享这些块。v8 引擎具有 30K 的流式处理/分析阈值。这意味着所有小于 30K 的块都将在关键加载路径上解析,即使它是非关键的。

由于上述原因,对于相同的代码,较大的块可能比较小的块更有效,以优化下载和浏览器性能。

2、提高缓存命中率和缓存效率:较小的块可提高缓存效率,尤其是对于以增量方式加载 JS 的应用。

  • 更改被隔离为具有较小块的较少块。如果发生代码更改,则只需要重新下载受影响的块,并且与这些块对应的代码大小可能很小。因此,可以在缓存中找到剩余的块,从而增加缓存命中的数量。
  • 对于较大的块,可能会影响大量代码,并且需要在代码更改后重新下载。

因此,较小的块是利用缓存机制的理想。

3、**快速执行 **- 要使代码快速执行,它应满足以下条件。

  • 所有必需的依赖项都随时可用 - 它们已一起下载或在缓存中可用。这意味着您应该将所有相关代码捆绑在一起,作为一个更大的块。
  • 只应执行页面/路由所需的代码。这要求不下载或执行额外的代码。包含常见依赖项的commons块可能具有大多数(但不是所有页面)所需的依赖项。重复数据删除代码需要更小的独立块。
  • 主线程上的长任务可能会长时间阻塞它。因此,这些需要分解成更小的块。

如上面的三角形所示,试图优化上述目标之一的加载粒度可能会使您远离其他目标。这就是粒度权衡的问题。

重复数据删除和缓存与浏览器性能和压缩不一致。

由于这种权衡,大多数生产应用程序目前使用的最大块数约为 10 个。需要增加此限制,以支持对具有大量 JavaScript 的应用进行更好的缓存和重复数据消除。

SplitChunksPlugin 和 Granular chunking

粒度权衡的潜在解决方案将满足以下要求。

1、允许使用更小的块大小来容纳更多块(40 到 100 个),以便在不影响性能的情况下更好地缓存和删除重复数据。

2、解决由于 IPC、I/O 和许多脚本标记的处理成本而导致的多个较小块的性能开销。

3、解决多个较小块情况下的压缩损失问题。

满足这些要求的潜在解决方案仍在开发中。但是,Webpack v4 的 SplitChunksPlugin 和粒度分块策略可以在一定程度上帮助增加加载粒度。

早期版本的 Webpack 使用 CommonsChunkPlugin 将公共依赖项或共享模块捆绑到单个块中。这可能会导致不使用这些通用模块的页面的下载和执行时间不必要地增加。为了更好地优化这些页面,Webpack 在 v4 中引入了 SplitChunksPlugin。根据默认值或配置创建多个拆分块,以防止跨各种路由获取重复代码。

Next.js采用了 SplitChunksPlugin 并实现了以下 Granular Chunking 策略来生成解决粒度权衡的 Webpack 块。

  • 任何足够大的第三方模块(大于 160 KB)都会被拆分为单独的块。
  • 为框架依赖项创建单独的框架块。(react、react-dom 等)
  • 根据需要创建任意数量的共享块。(最多 25 个)
  • 要生成的块的最小大小更改为 20 KB。

发出多个共享块而不是单个块可以最大程度地减少在不同页面上下载或执行的不必要(或重复)代码的数量。为大型第三方库生成独立的块可以改进缓存,因为它们不太可能经常更改。最小块大小为 20 kB,可确保压缩损耗相当低。

精细的分块策略帮助几个 Next JS 应用程序减少了网站使用的 JavaScript 总量。

在 Gatsby 中也实施了颗粒化分块策略,观察到类似的益处。

结论 

仅靠压缩并不能解决所有 JavaScript 性能问题,但了解浏览器和打包程序在幕后的工作方式有助于创建更好的打包策略,从而支持更好的压缩。加载粒度问题需要在生态系统中的不同平台上解决。颗粒分块可能是朝着这个方向迈出的一步,但我们还有很长的路要走。

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值