即时导航体验

将 Service Worker 与传统的预加载技术相结合。

Demián Renzulli
Demián Renzulli
Gilberto Cocchi
Gilberto Cocchi

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

从技术层面来讲,浏览不同页面意味着发出导航请求。一般来说,您希望使用长效 Cache-Control 标头来缓存导航请求的 HTML 响应。通常,应通过网络使用 Cache-Control: no-cache 满足这些请求,以确保 HTML 以及后续网络请求链(在合理范围内)保持新鲜。遗憾的是,每次用户导航到新网页时都必须与网络进行交互,这意味着每次导航都可能会很慢,至少意味着导航速度不会可靠地快。

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

在本指南中,我们将探索如何将服务工件用作传统预加载技术的补充。

生产案例

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

MercadoLibre 的商品详情页面 1 和 2 的屏幕截图,以及将这两个页面关联起来的链接预提取代码。

系统会以“最低”优先级请求预提取的文件,并将其存储在 HTTP 缓存内存缓存(具体取决于资源是否可缓存)中,存储时间因浏览器而异。例如,从 Chrome 85 开始,此值为 5 分钟。资源会保留 5 分钟,之后系统会应用资源的常规 Cache-Control 规则。

使用服务工作器缓存可以帮助您将预提取资源的生命周期延长到 5 分钟的时间范围之外。

例如,意大利体育门户网站 Virgilio Sport 使用服务工件预加载其首页中最热门的帖子。它们还使用 Network Information API 来避免为使用 2G 连接的用户预加载。

Virgilio Sport 徽标。

因此,在 3 周的观察期内,Virgilio Sport 发现,导航到文章的加载时间缩短了 78%,文章展示次数增加了 45%

Virgilio Sport 首页和文章页面的屏幕截图,其中显示了预加载后的效果指标。

使用 Workbox 实现预缓存

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

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 分钟。

借助服务工作器,您可以延长预提取页面的生命周期,同时还能让这些资源可供离线使用,从而获得额外的好处。

在上面的示例中,您可以使用 Workbox 运行时缓存策略来补充用于预加载商品页面的 <link rel="prefetch">

如需实现此功能,请执行以下操作:

  • 向网页添加 <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
    }),
  ],
});

在本例中,我们选择使用“在重新验证期间使用过时数据”策略。在此策略中,可以同时从缓存和网络请求网页。响应来自缓存(如果有),否则来自网络。每次成功请求时,缓存都会始终与网络响应保持最新状态。

3. 将预加载委托给 Service Worker

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

不过,在某些情况下,最好将此任务完全委托给服务工件。例如:如需预加载客户端呈现的产品详情页面中的前几个产品,您可能需要根据 API 响应在该页面中动态注入多个 <link rel="prefetch"> 代码。这可能会暂时占用网页主线程的时间,并使实现变得更加困难。

在这种情况下,请使用“页面到服务工作器通信策略”,将预加载任务完全委托给服务工作器。您可以使用 worker.postMessage() 实现此类通信:

与服务工件进行双向通信的网页的图标。

Workbox Window 软件包简化了此类通信,提取了正在执行的底层调用的许多详细信息。

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

  • 在网页中:调用 service worker,并向其传递消息类型和要预加载的网址列表:
const wb = new Workbox('/sw.js');
wb.register();

const prefetchResponse = await wb.messageSW({type: 'PREFETCH_URLS', urls: […]});
  • 在服务工件中:实现消息处理脚本,以便针对要预加载的每个网址发出 fetch() 请求:
addEventListener('message', (event) => {
  if (event.data.type === 'PREFETCH_URLS') {
    // Fetch URLs and store them in the cache
  }
});