Codelab:预加载关键资源以提高加载速度

在此 Codelab 中,我们通过预加载和预提取一些资源来提升以下网页的性能:

应用屏幕截图

测量

请先衡量网站的效果,然后再添加任何优化措施。

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

对 Glitch 的正式版运行 Lighthouse 性能审核(依次选择 Lighthouse > Options > Performance)。另请参阅利用 Lighthouse 发现性能改进机会

Lighthouse 会针对提取较晚的资源显示以下失败的审核结果:

Lighthouse:预加载关键请求审核
  • 按 `Control+Shift+J`(在 Mac 上为 `Command+Option+J`)打开 DevTools。
  • 点击网络标签页。
显示迟发现的资源的“网络”面板

main.css 文件不是由放置在 HTML 文档中的 Link 元素 (<link>) 提取的,而是由单独的 JavaScript 文件 fetch-css.jswindow.onLoad 事件之后将 Link 元素附加到 DOM。这意味着,只有在浏览器完成解析和执行 JS 文件后,才会提取该文件。同样,只有在 CSS 文件下载完毕后,系统才会提取 main.css 中指定的 Web 字体 (K2D.woff2)。

关键请求链表示浏览器按优先级提取资源的顺序。此网页目前如下所示:

├─┬ / (initial HTML file)
  └── fetch-css.js
    └── main.css
      └── K2D.woff2

由于 CSS 文件位于请求链的第三个级别,因此 Lighthouse 将其识别为发现较晚的资源。

预加载关键资源

main.css 文件是一项关键资源,需要在页面加载后立即使用。对于在应用中较晚提取的重要文件(例如此资源),您可以使用链接预加载标记,通过向文档的标头添加 Link 元素,告知浏览器更快地下载该文件。

为此应用添加预加载标记:

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
</head>

as 属性用于确定要提取的资源类型,as="style" 用于预加载样式表文件。

重新加载应用,然后查看 DevTools 中的 Network 面板。

包含预加载资源的“网络”面板

请注意,浏览器在负责提取 CSS 文件的 JavaScript 尚未完成解析之前,就提取了 CSS 文件。使用预加载时,浏览器会假定资源对网页至关重要,并为其执行抢先提取。

如果使用不当,预加载可能会对性能造成不利影响,因为它会对未使用的资源发出不必要的请求。在此应用中,details.css 是位于项目根目录下的另一个 CSS 文件,但用于单独的 /details route。为了举例说明如何错误地使用预加载,请为此资源添加预加载提示。

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
  <link rel="preload" href="details.css" as="style">
</head>

重新加载应用,然后查看 Network 面板。 系统会发出检索 details.css 的请求,即使网页未使用该 cookie 也是如此。

包含不必要的预加载的“网络”面板

如果预加载的资源在加载几秒钟后未被页面使用,Chrome 会在控制台面板中显示警告。

控制台中的预加载警告

您可以将此警告作为指标,确定您的网页是否有任何预加载的资源未立即使用。您现在可以移除此网页的多余预加载链接。

<head>
  <!-- ... -->
  <link rel="preload" href="main.css" as="style">
  <link rel="preload" href="details.css" as="style">
</head>

如需查看可提取的所有资源类型的列表以及应为 as 属性使用的正确值,请参阅关于预加载的 MDN 文章

预提取未来资源

预提取是另一种浏览器提示,可用于请求用于其他导航路线的资源,但其优先级低于当前网页所需的其他重要资源。

在该网站中,点击该图片会将您定向到单独的 details/ 路由。

详细路线

单独的 CSS 文件 details.css 包含此简单页面所需的所有样式。向 index.html 添加链接元素以预加载此资源。

<head>
  <!-- ... -->
  <link rel="prefetch" href="details.css">
</head>

如需了解这会如何触发对文件的请求,请在开发者工具中打开网络面板,然后取消选中停用缓存选项。

在 Chrome 开发者工具中停用缓存

重新加载应用,并注意在提取所有其他文件后,系统如何针对 details.css 发出优先级极低的请求。

包含预提取资源的“网络”面板

打开 DevTools 后,点击网站上的图片以前往 details 页面。 由于 details.html 中使用链接元素提取 details.css,因此系统会按预期对资源发出请求。

详情页面网络请求

在 DevTools 中点击 details.css 网络请求,查看其详细信息。您会注意到,系统会从浏览器的磁盘缓存中检索文件。

从磁盘缓存提取的详细信息请求

借助浏览器空闲时间,预提取功能会提前请求其他网页所需的资源。这样一来,浏览器便能更快地缓存资源,并在需要时从缓存中提供资源,从而加快未来的导航请求速度。

使用 Webpack 进行预加载和预提取

利用代码拆分方法减少 JavaScript 负载一文探讨了如何使用动态导入将软件包拆分为多个分块。下面的简单应用演示了这一点,该应用会在提交表单时动态导入 Lodash 中的模块。

演示代码分块的 Magic Sorter 应用

您可以点击此处查看此应用的 Glitch。

位于 src/index.js, 中的以下代码块负责在用户点击按钮时动态导入方法。

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

拆分软件包可缩减其初始大小,从而缩短网页加载时间。webpack 4.6.0 版本支持预加载或预提取动态导入的块。以此应用为例,可以在浏览器空闲时预提取 lodash 方法;当用户按下按钮时,系统无需延迟即可提取资源。

在动态导入中使用特定的 webpackPrefetch 注释参数以预加载特定分块。下面是此特定应用的显示效果。

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

重新加载应用后,webpack 会将资源的预提取标记注入文档的标头中。您可以在 DevTools 的元素面板中看到这一点。

包含预提取标记的“元素”面板

观察 Network 面板中的请求还会发现,在请求所有其他资源后,系统会以低优先级提取此分块。

包含预提取请求的“网络”面板

虽然预加载更适用于此用例,但 webpack 还支持预加载动态导入的块。

import(/* webpackPreload: true */ 'module')

总结

通过此 Codelab,您应该能够充分了解预加载或预提取某些资源如何改善您网站的用户体验。需要注意的是,这些技术不适用于每种资源,如果使用不当,可能会影响性能。只选择性地预加载或预提取资源,可获得最佳效果。

总结:

  • 对较晚发现但对当前网页至关重要的资源使用预加载
  • 针对未来的导航路线或用户操作所需的资源使用预提取

目前,并非所有浏览器都同时支持预加载和预提取。这意味着,应用的所有用户都可能不会注意到性能提升。

如需详细了解预加载和预提取对网页有何影响的具体方面,请参阅以下文章: