即时导航体验

使用 Service Worker 补充传统的预提取技术。

在网站上执行任务通常涉及多个步骤。例如,在电子商务网站上购买产品可能涉及搜索产品、从结果列表中选择商品、将商品添加到购物车,以及通过结账完成操作。

从技术角度来看,在不同页面之间移动意味着发出导航请求。作为一般规则,您希望使用长期有效的 Cache-Control 标头来缓存导航请求的 HTML 响应。它们通常应通过网络满足,并使用 Cache-Control: no-cache,以确保 HTML 以及后续网络请求链是(合理地)新鲜的。不幸的是,每次用户导航到新页面时都必须访问网络意味着每次导航都可能很慢——至少,这意味着它不会可靠地快速。

为了加快这些请求的速度,如果您可以预测用户的操作,则可以预先请求这些页面和资源,并将它们在缓存中保留一小段时间,直到用户点击这些链接。此技术称为预提取,它通常通过向页面添加 <link rel="prefetch"> 标记来实现,以指示要预提取的资源。

在本指南中,我们将探讨如何使用 Service Worker 作为传统预提取技术的补充。

生产案例

MercadoLibre 是拉丁美洲最大的电子商务网站。为了加快导航速度,他们在流程的某些部分动态注入 <link rel="prefetch"> 标记。例如,在列表页面中,他们会在用户滚动到列表底部后立即提取下一个结果页面

Screenshot of MercadoLibre's listing pages one and two and a Link Prefetch tag connecting both.

预提取的文件以“最低”优先级请求,并存储在 HTTP 缓存内存缓存中(取决于资源是否可缓存),存储时间长短因浏览器而异。例如,截至 Chrome 85,此值为 5 分钟。资源保留约五分钟,之后应用资源的正常 Cache-Control 规则。

使用 Service Worker 缓存可以帮助您延长预提取资源的生命周期,使其超出五分钟的窗口期。

例如,意大利体育门户网站 Virgilio Sport 使用 Service Worker 预提取其主页上最受欢迎的帖子。他们还使用 网络信息 API 来避免为 2G 连接用户进行预提取。

Virgilio Sport logo.

因此,在为期 3 周的观察中,Virgilio Sport 见证了文章导航加载时间提高了 78%,文章展示次数增加了 45%

A screenshot of Virgilio Sport home and article pages, with impact metrics after prefetching.

使用 Workbox 实现预缓存

在以下部分中,我们将使用 Workbox 来展示如何在 Service Worker 中实现不同的缓存技术,这些技术可以用作 <link rel="prefetch"> 的补充,甚至可以替代它,方法是将此任务完全委托给 Service Worker。

1. 预缓存静态页面和页面子资源

预缓存是 Service Worker 在安装时将文件保存到缓存的能力。

在以下情况下,预缓存用于实现与预提取类似的目标:加快导航速度。

预缓存静态页面

对于在构建时生成的页面(例如 about.htmlcontact.html)或完全静态的站点,只需将站点的文档添加到预缓存列表即可,这样每次用户访问它们时,它们都已在缓存中可用

workbox.precaching.precacheAndRoute([
  {url: '/about.html', revision: 'abcd1234'},
  // ... other entries ...
]);

预缓存页面子资源

预缓存站点的不同部分可能使用的静态资源(例如 JavaScript、CSS 等)是一种通用的最佳实践,可以在预提取场景中提供额外的提升。

为了加快电子商务网站的导航速度,您可以在列表页面中使用 <link rel="prefetch"> 标记来预提取列表页面中前几个产品的产品详情页面。如果您已经预缓存了产品页面子资源,这可以使导航速度更快。

要实现此目的

  • 向页面添加 <link rel="prefetch"> 标记
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • 将页面子资源添加到 Service Worker 中的预缓存列表
workbox.precaching.precacheAndRoute([
  '/styles/product-page.ac29.css',
  // ... other entries ...
]);

2. 延长预提取资源的生命周期

如前所述,<link rel="prefetch"> 会提取资源并将其在 HTTP 缓存中保留有限的时间,之后将应用资源的 Cache-Control 规则。截至 Chrome 85,此值为 5 分钟。

Service Worker 允许您延长预提取页面的生命周期,同时提供使这些资源可用于离线使用的额外好处。

在上一个示例中,可以将用于预提取产品页面的 <link rel="prefetch">Workbox 运行时缓存策略结合使用。

要实现该目的

  • 向页面添加 <link rel="prefetch"> 标记
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • 在 Service Worker 中为这些类型的请求实现运行时缓存策略
new workbox.strategies.StaleWhileRevalidate({
  cacheName: 'document-cache',
  plugins: [
    new workbox.expiration.Plugin({
      maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
    }),
  ],
});

在本例中,我们选择使用 stale-while-revalidate 策略。在此策略中,可以并行地从缓存和网络请求页面。响应来自缓存(如果可用),否则来自网络。缓存始终通过每次成功的请求与网络响应保持同步。

3. 将预提取委托给 Service Worker

在大多数情况下,最好的方法是使用 <link rel="prefetch">。该标记是一种资源提示,旨在使预提取尽可能高效。

但在某些情况下,将此任务完全委托给 Service Worker 可能更好。例如:为了预提取客户端渲染的产品列表页面中的前几个产品,可能需要基于 API 响应在页面中动态注入多个 <link rel="prefetch"> 标记。这可能会暂时占用页面主线程上的时间,并使实现更加困难。

在这种情况下,请使用“页面到 Service Worker 通信策略”,将预提取任务完全委托给 Service Worker。这种类型的通信可以通过使用 worker.postMessage() 来实现

An icon of a page making two way communication with a service worker.

Workbox Window 程序包简化了这种类型的通信,抽象了正在完成的底层调用的许多细节。

使用 Workbox Window 进行预提取可以通过以下方式实现

  • 在页面中:调用 Service Worker,向其传递消息类型和要预提取的 URL 列表
const wb = new Workbox('/sw.js');
wb.register();

const prefetchResponse = await wb.messageSW({type: 'PREFETCH_URLS', urls: []});
  • 在 Service Worker 中:实现消息处理程序,以便为每个要预提取的 URL 发出 fetch() 请求
addEventListener('message', (event) => {
  if (event.data.type === 'PREFETCH_URLS') {
    // Fetch URLs and store them in the cache
  }
});