使用 gzip 压缩网络载荷

此 Codelab 探讨了如何通过缩减以下应用的请求大小来缩减和压缩以下应用的 JavaScript 软件包,从而提升网页性能。

应用屏幕截图

测量

在深入了解添加优化措施之前,最好先分析应用的当前状态。

  • 如需预览网站,请按查看应用,然后按全屏 全屏

移除未使用的代码 Codelab 对此应用也有过介绍,您可以通过此应用为您最喜欢的小猫投票。🐈

现在来看看此应用的大小:

  1. 按 `Control+Shift+J`(在 Mac 上,则按 `Command+Option+J`)打开开发者工具。
  2. 点击网络标签页。
  3. 选中停用缓存复选框。
  4. 重新加载应用。

“网络”面板中的原始文件包大小

虽然在缩减此 bundle 大小方面,“Remove unused code” Codelab 中取得了很大进展,但 225 KB 仍然相当大。

缩减大小

请参考以下代码块。

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

如果此函数保存在各自的文件中,则文件大小约为 112 B(字节)

如果移除所有空格,则生成的代码将如下所示:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

文件大小现在约为 83 B。如果通过缩短变量名称的长度和修改一些表达式进一步损坏变量,则最终代码可能如下所示:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

文件大小现在达到 62 B

每一步,代码都会变得越来越难以阅读。不过,浏览器的 JavaScript 引擎会以完全相同的方式解读这些内容。以这种方式混淆代码的优势有助于减小文件大小。112B 最初并不多,但仍然缩减了 50%!

在此应用中,webpack 版本 4 用作模块打包器。具体版本可以在 package.json 中查看。

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

默认情况下,版本 4 在生产模式下会缩减 bundle 大小。它使用 Terser 插件 TerserWebpackPlugin。Terser 是用于压缩 JavaScript 代码的常用工具。

如需了解缩减后的代码是什么样子,请继续点击 main.bundle.js,同时仍在开发者工具的 Network 面板中。现在,点击响应标签页。

缩减响应大小

经过缩减和损坏的最终形式代码显示在响应正文中。如需了解未缩减 bundle 的大小,请打开 webpack.config.js 并更新 mode 配置。

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

重新加载应用,并通过开发者工具的 Network 面板再次查看软件包大小

软件包大小为 767 KB

这差不多!😅

请务必先还原此处的更改,然后再继续操作。

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

在应用中添加缩减代码大小的流程取决于您使用的工具:

  • 如果使用 webpack v4 或更高版本,则无需执行任何额外工作,因为在生产模式下,代码默认会缩减。👍
  • 如果使用的是旧版 webpack,请安装 TerserWebpackPlugin 并将其包含在 webpack 构建流程中。该文档对此进行了详细说明。
  • 您也可以使用其他缩减插件,例如 BabelMinifyWebpackPluginClosureCompilerPlugin
  • 如果根本没有使用模块打包器,请使用 Terser 作为 CLI 工具,或直接将其添加为依赖项。

压缩

虽然“压缩”这一术语有时广泛用于解释在缩减过程中如何缩减代码,但实际上它并没有就字面意义进行压缩。

“压缩”通常是指使用数据压缩算法修改过的代码。压缩代码与最终提供完全有效的代码的缩减功能不同,在使用压缩代码之前需要先将其解压缩

对于每个 HTTP 请求和响应,浏览器和网络服务器都可以添加headers,以包含有关要提取或接收的资源的更多信息。您可以在开发者工具的 Network 面板的 Headers 标签页中看出这一点,其中展示了三种类型:

  • General 表示与整个请求-响应互动相关的常规标头。
  • 响应标头会显示特定于服务器实际响应的标头列表。
  • 请求标头显示客户端附加到请求的标头列表。

请查看 Request Headers 中的 accept-encoding 标头。

接受编码标头

浏览器使用 accept-encoding 指定其支持的内容编码格式或压缩算法。市面上有很多文本压缩算法,但此处只有三种支持 HTTP 网络请求的压缩(和解压缩)算法:

  • Gzip (gzip):用于服务器和客户端交互的最广泛压缩格式。它基于 Deflate 算法构建,当前的所有浏览器都支持该算法。
  • 压缩 (deflate):不常用。
  • Brotli (br):这是一种较新的压缩算法,旨在进一步提高压缩比,从而加快网页加载速度。大多数浏览器的最新版本都支持此功能。

除了 Express 现已用作服务器框架之外,本教程中的示例应用与“移除未使用的代码” Codelab 中完成的应用完全相同。在接下来的几个部分中,我们将介绍静态压缩和动态压缩。

动态压缩

动态压缩涉及当浏览器请求时即时压缩资源。

优点

  • 不需要创建和更新资源的已保存压缩版本。
  • 即时压缩功能尤其适用于动态生成的网页。

缺点

  • 若要提高压缩比,以更高的级别压缩文件,需要花费更长时间。当用户等待资源在服务器发送之前进行压缩时,这可能会导致性能下降。

使用 Node/Express 动态压缩

server.js 文件负责设置托管应用的节点服务器。

const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

目前所做的只是导入 express,并使用 express.static 中间件加载 public/ 目录中的所有静态 HTML、JS 和 CSS 文件(这些文件由 webpack 在每次构建时创建)。

为了确保每次收到请求时都会压缩所有资源,可以使用压缩中间件库。首先,在 package.json 中将其作为 devDependency 添加:

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

然后将其导入到服务器文件 server.js 中:

const express = require('express');
const compression = require('compression');

在装载 express.static 之前,请将其作为中间件添加:

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

现在,重新加载应用,并在 Network 面板中查看 bundle 大小。

采用动态压缩的软件包大小

从 225 KB 到 61.6 KB!现在,在 Response Headers 中,content-encoding 标头显示服务器正在发送这个使用 gzip 编码的文件。

内容编码标头

静态压缩

静态压缩背后的理念是预先压缩并保存资源。

优点

  • 因此,您不必再担心因压缩级别过高而导致的延迟。无需任何动态文件即可压缩文件,因为文件现在可以直接提取。

缺点

  • 每次构建时都需要压缩资源。如果使用高压缩级别,构建时间可能会显著增加。

使用 Node/Express 和 webpack 进行静态压缩

由于静态压缩涉及提前压缩文件,因此可以在构建步骤中修改 webpack 设置以压缩资源。CompressionPlugin 可用于此目的。

首先,在 package.json 中将其作为 devDependency 添加:

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

与任何其他 Webpack 插件一样,请将其导入到配置文件 webpack.config.js:

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

并将其添加到 plugins 数组中:

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

默认情况下,该插件使用 gzip 压缩 build 文件。请查看该文档,了解如何添加选项来使用其他算法或包含/排除某些文件。

当应用重新加载并重新构建时,现在会创建主软件包的压缩版本。打开 Glitch 控制台,查看节点服务器提供的最终 public/ 目录中的内容。

  • 点击工具按钮。
  • 点击控制台按钮。
  • 在控制台中,运行以下命令切换到 public 目录并查看其所有文件:
cd public
ls

最终输出的文件位于公共目录中

软件包的 Gzip 版本 main.bundle.js.gz 现在也保存在此处。默认情况下,CompressionPlugin 也会压缩 index.html

接下来,您需要让服务器在每次收到原始 JS 版本请求时发送这些经过 gzip 压缩的文件。这可以通过在 server.js 中定义一个新路由来实现,然后再使用 express.static 传送文件。

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});

app.use(express.static('public'));

//...

app.get 用于告知服务器如何响应针对特定端点的 GET 请求。然后,会使用回调函数定义如何处理此请求。该路线的工作原理如下:

  • 指定 '*.js' 作为第一个参数意味着这适用于为提取 JS 文件而触发的每个端点。
  • 在回调中,.gz 会附加到请求的网址,并且 Content-Encoding 响应标头会设置为 gzip
  • 最后,next() 可确保序列继续执行下一个可能进行的回调。

应用重新加载后,请再次查看 Network 面板。

通过静态压缩缩减软件包大小

和之前一样,软件包大小大幅缩减!

总结

此 Codelab 介绍了缩减源代码大小和压缩源代码的过程。这两种方法在当今许多可用工具中都已成为默认方法,因此请务必了解您的工具链是否已支持这些方法,或者您是否应该开始应用这两个进程。