通过代码拆分减少 JavaScript 载荷

大多数网页和应用均由许多不同的部分组成。将 JavaScript 拆分为多个区块可提高网页性能,而不是在第一个页面加载完毕后立即发送构成应用的所有 JavaScript。

此 Codelab 介绍了如何使用代码拆分提升一个对三位数进行排序的简单应用的性能。

一个浏览器窗口,显示了一款名为“Magic Sorter”的应用,该应用包含三个用于输入数字的字段和一个排序按钮。

测量

与往常一样,请务必先衡量网站的表现,然后再尝试添加任何优化措施。

  1. 如需预览网站,请按查看应用,然后按全屏 全屏
  2. 按 `Control+Shift+J`(在 Mac 上,则按 `Command+Option+J`)打开开发者工具。
  3. 点击网络标签页。
  4. 选中停用缓存复选框。
  5. 重新加载应用。

显示 71.2 KB JavaScript 软件包的网络面板。

价值 71.2 KB 的 JavaScript 代码,只是为了在一个简单的应用中对几个数字进行排序。 这是怎么回事?

在源代码 (src/index.js) 中,导入并使用 lodash 库。Lodash 提供了许多实用的实用函数,但此处只使用了软件包中的一个方法。在只利用一小部分第三方依赖项的情况下,安装和导入完整的第三方依赖项是一种常见的错误。

优化

您可以通过以下几种方式缩减软件包大小:

  1. 编写自定义排序方法,而不是导入第三方库
  2. 使用内置的 Array.prototype.sort() 方法按数字排序
  3. 仅从 lodash(而非整个库)导入 sortBy 方法
  4. 仅在用户点击按钮时下载用于排序的代码

方案 1 和 2 都是缩减软件包大小的理想方法(并且可能最适合实际应用)。但是,本教程中并不出于教学目的而使用它们 😈?。

选项 3 和 4 都有助于提升此应用的性能。此 Codelab 的后续部分将介绍这些步骤。与任何编码教程一样,请始终尝试自行编写代码,而不是复制和粘贴。

仅导入需要的内容

需要修改几个文件,以便仅从 lodash 导入单个方法。首先,替换 package.json 中的此依赖项:

"lodash": "^4.7.0",

替换为:

"lodash.sortby": "^4.7.0",

现在,在 src/index.js 中,导入以下特定模块:

import "./style.css";
import _ from "lodash";
import sortBy from "lodash.sortby";

并更新值的排序方式:

form.addEventListener("submit", e => {
  e.preventDefault();
  const values = [input1.valueAsNumber, input2.valueAsNumber, input3.valueAsNumber];
  const sortedValues = _.sortBy(values);
  const sortedValues = sortBy(values);

  results.innerHTML = `
    <h2>
      ${sortedValues}
    </h2>
  `
});

重新加载应用,打开开发者工具,然后再次查看 Network 面板。

显示 15.2 KB JavaScript 软件包的网络面板。

对于此应用,软件包大小缩减了 4 倍以上,而且工作量非常少,但仍有很大的改进空间。

代码拆分

webpack 是当今最受欢迎的开源模块打包器之一。简而言之,它将构成 Web 应用的所有 JavaScript 模块(以及其他资源)捆绑为浏览器可读取的静态文件。

此应用中使用的单个软件包可拆分为两个单独的区块:

  • 负责构成初始路由的代码
  • 包含排序码的次级区块

通过使用动态导入,系统可以延迟加载或按需加载辅助分块。在此应用中,构成数据块的代码只能在用户按下按钮时加载。

首先,移除 src/index.js 中排序方法的顶级导入:

import sortBy from "lodash.sortby";

然后将其导入到按下按钮时触发的事件监听器:

form.addEventListener("submit", e => {
  e.preventDefault();
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

import() 功能是提案(目前位于 TC39 流程的第 3 阶段)的一部分,旨在提供动态导入模块的功能。webpack 已支持此功能,并遵循提案中列出的相同语法。

import() 会返回一个 promise;当它进行解析时,系统会提供所选模块,该模块将拆分为单独的分块。返回模块后,module.default 用于引用 lodash 提供的默认导出内容。该 promise 会与另一个 .then 链接,后者会调用 sortInput 方法来对三个输入值进行排序。在 promise 链的末尾,.catch() 用于处理 promise 因错误而被拒的情况。

您需要做的最后一件事就是在文件末尾编写 sortInput 方法。这必须是一个函数,该函数可返回接受从 lodash.sortBy 导入的方法的函数。然后,嵌套函数就可以对三个输入值进行排序并更新 DOM。

const sortInput = () => {
  return (sortBy) => {
    const values = [
      input1.valueAsNumber,
      input2.valueAsNumber,
      input3.valueAsNumber
    ];
    const sortedValues = sortBy(values);

    results.innerHTML = `
      <h2>
        ${sortedValues}
      </h2>
    `
  };
}

监控

最后一次重新加载应用,并再次密切关注 Network 面板。应用加载后,系统只会下载较小的初始软件包。

显示 2.7 KB JavaScript 软件包的网络面板。

按下该按钮对输入数字进行排序后,系统会获取并执行包含排序代码的分块。

网络面板,其中显示 2.7 KB JavaScript 软件包,后跟 13.9 KB JavaScript 软件包。

请注意,数字仍然是如何排序的!

总结

代码拆分和延迟加载是缩减应用初始软件包大小的实用方法,并且可直接显著缩短页面加载时间。不过,在应用中添加这项优化之前,需要考虑一些重要事项。

延迟加载界面

延迟加载特定代码模块时,请务必考虑网络连接信号较弱的用户会获得怎样的体验。当用户提交操作时,如果拆分并加载一大段代码,看起来就像应用已停止运行,那么不妨考虑显示某种类型的加载指示器。

延迟加载第三方节点模块

这并非始终是在应用中延迟加载第三方依赖项的最佳方法,具体取决于您在哪里使用它们。通常,第三方依赖项会拆分为单独的 vendor 软件包,由于其更新频率较低,可缓存该软件包。详细了解 SplitChunksPlugin 如何帮助您执行此操作。

使用 JavaScript 框架进行延迟加载

许多使用 Webpack 的热门框架和库都提供抽象化,与在应用中使用动态导入相比,这样能够更轻松地实现延迟加载。

虽然了解动态导入的工作原理很有用,但请始终使用框架/库建议的方法延迟加载特定模块。

预加载和预提取

请尽可能利用浏览器提示(例如 <link rel="preload"><link rel="prefetch">)来尝试尽快加载关键模块。webpack 通过在 import 语句中使用魔法注释来支持这两种提示。预加载关键分块指南对此进行了更详细的说明。

延迟加载(不仅仅是代码)

图片可能会构成应用的重要组成部分。延迟加载非首屏或设备视口以外的区域可以加快网站加载速度。如需了解详情,请参阅 Lazysizes 指南。