使用 gzip 压缩网络载荷

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

应用屏幕截图

测量

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

  • 如需预览网站,请按 View App(查看应用)。然后按 Fullscreen(全屏)全屏

此应用也曾在“移除未使用的代码” Codelab 中介绍过,您可以通过此应用为自己喜欢的小猫咪投票。🐈?

现在,我们来看看这个应用有多大:

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

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

虽然在“移除未使用的代码” 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 引擎会以完全相同的方式解读这两者。以这种方式混淆代码的好处在于,可以缩减文件大小。112 B 在开始时确实不算多,但体积还是缩小了 50%!

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

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

版本 4 在生产模式下默认会缩减软件包。它使用 TerserWebpackPlugin,这是一个 Terser 插件。Terser 是一款用于压缩 JavaScript 代码的热门工具。

如需大致了解经过缩减的代码的样子,请继续点击 main.bundle.js,同时仍在 DevTools 的网络面板中。现在,点击回复标签页。

精简版响应

响应正文中会显示经过缩减和混淆处理的最终代码。如需了解未缩减的软件包可能有多大,请打开 webpack.config.js 并更新 mode 配置。

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

重新加载应用,然后通过 DevTools 的 Network 面板再次查看 bundle 大小

767 KB 的 Bundle 大小

差异很大!😅

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

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

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

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

压缩

虽然有时会笼统地使用“压缩”一词来解释在缩减过程中如何减少代码,但实际上,代码并未按字面意思进行压缩。

压缩通常是指使用数据压缩算法修改的代码。与最终提供完全有效代码的缩减不同,压缩的代码需要先解压缩才能使用。

在每次 HTTP 请求和响应中,浏览器和网络服务器都可以添加标头,以包含有关要提取或接收的资源的其他信息。您可以在开发者工具“网络”面板的 Headers 标签页中看到这三种类型:

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

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

Accept-Encoding 标头

浏览器使用 accept-encoding 指定其支持的内容编码格式或压缩算法。有许多文本压缩算法,但这里仅支持三种用于压缩(和解压缩)HTTP 网络请求的算法:

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

本教程中的示例应用与在“移除未使用的代码” Codelab 中完成的应用完全相同,唯一的区别是 Express 现在用作服务器框架。在接下来的几个部分中,我们将探讨静态和动态压缩。

动态压缩

动态压缩涉及在浏览器请求素材资源时动态压缩素材资源。

优点

  • 无需创建和更新已保存的压缩版资源。
  • 动态压缩特别适用于动态生成的网页。

缺点

  • 在更高级别压缩文件以实现更好的压缩比需要更长时间。这可能会导致性能下降,因为用户需要等待服务器压缩资源,然后才能发送资源。

使用 Node/Express 进行动态压缩

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

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 面板中的软件包大小。

启用动态压缩后的软件包大小

从 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 控制台,查看由 Node 服务器提供的最终 public/ 目录中的内容。

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

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

现在,此处还会保存经过 GZIP 压缩的 bundle 版本 main.bundle.js.gzCompressionPlugin 还会默认压缩 index.html

接下来需要做的是,告知服务器在每次请求原始 JS 版本时发送这些经过 GZIP 压缩的文件。为此,您可以在使用 express.static 分发文件之前在 server.js 中定义新的路由。

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 介绍了缩减和压缩源代码的过程。这两种技术正在成为当今许多工具的默认配置,因此请务必了解您的工具链是否已支持它们,或者您是否应开始自行应用这两种流程。