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

Houssein Djirdeh
Houssein Djirdeh

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

应用屏幕截图

测量

首先,在添加任何优化之前,先衡量网站的效果。

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

对 Glitch 的实时版本运行 Lighthouse 性能审核(Lighthouse > Options > Performance)(另请参阅利用 Lighthouse 发现性能提升机会)。

对于提取较晚的资源,Lighthouse 会显示以下失败的审核:

Lighthouse:预加载关键请求审核
  • 按 `Control+Shift+J`(在 Mac 上,按 `Command+Option+J`)打开开发者工具。
  • 点击网络标签页。
包含后期发现的资源的“网络”面板

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

关键请求链表示浏览器优先处理和提取的资源顺序。对于此网页,目前它看起来像这样:

├─┬ / (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" 用于预加载样式表文件。

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

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

请注意,浏览器在负责提取 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,也会发出检索该 details.css 的请求。

具有不必要预加载的网络面板

当预加载的资源在加载后几秒钟内未被网页使用时,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,因此系统会按预期请求资源。

详情页面网络请求

点击开发者工具中的 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 会将资源的预提取标记注入到文档的头部。您可以在开发者工具的元素面板中看到这一点。

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

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

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

虽然预提取更适合此用例,但 webpack 也支持预加载动态导入的块。

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

总结

完成此 Codelab 后,您应该能够充分了解预加载或预提取某些资源如何提升网站的用户体验。需要重点说明的是,这些技术不应应用于所有资源,使用不当可能会损害性能。只有在有选择地预加载或预提取时,才能获得最佳效果。

总结:

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

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

如果您想详细了解预加载和预提取如何影响网页的特定方面,请参阅以下文章: