通过代码拆分减少 JavaScript 载荷

Houssein Djirdeh
Houssein Djirdeh

大多数网页和应用都由许多不同的部分组成。与在加载第一个页面时立即发送构成应用的所有 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。 What gives?

在源代码 (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>
  `
});

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

显示 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,当该 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>
    `
  };
}

监控

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

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

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

“网络”面板显示了 2.7 KB 的 JavaScript 软件包,随后是 13.9 KB 的 JavaScript 软件包。

请注意,数字仍会进行排序!

总结

代码拆分和延迟加载是非常有用的技术,可用于缩减应用的初始软件包大小,从而直接大幅缩短网页加载时间。不过,在将此优化纳入应用之前,需要考虑一些重要事项。

延迟加载界面

在延迟加载特定代码模块时,请务必考虑网络连接较弱的用户体验。当用户提交操作时,拆分并加载非常大的代码块可能会使应用看起来好像停止了工作,因此请考虑显示某种加载指示器。

延迟加载第三方 Node 模块

在应用中延迟加载第三方依赖项并不总是最佳方法,具体取决于您在何处使用这些依赖项。通常,第三方依赖项会拆分为单独的 vendor 软件包,由于它们不会经常更新,因此可以缓存。详细了解 SplitChunksPlugin 如何帮助您实现此目的。

使用 JavaScript 框架进行延迟加载

许多使用 webpack 的热门框架和库都提供了抽象概念,可让您更轻松地实现延迟加载,而无需在应用中间使用动态导入。

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

预加载和预提取

如果可以,请利用 <link rel="preload"><link rel="prefetch"> 等浏览器提示,尝试更快地加载关键模块。webpack 通过在 import 语句中使用 magic comment 来支持这两种提示。预加载关键块指南中对此进行了更详细的说明。

延迟加载更多代码

图片可能占应用的重要部分。延迟加载折叠线下方或设备视口之外的图片可以加快网站速度。如需详细了解此主题,请参阅 Lazysizes 指南。