利用 Service Worker 补充传统的预提取技术。
在网站上执行任务通常需要完成多个步骤。例如,在电子商务网站上购买商品可能涉及搜索商品、从结果列表中选择商品、将商品添加到购物车,以及通过结账完成操作。
从技术层面来讲,浏览不同页面意味着发出导航请求。一般来说,您不希望使用长效 Cache-Control
标头来缓存导航请求的 HTML 响应。一般情况下,应使用 Cache-Control: no-cache
通过网络满足这些条件,以确保 HTML 以及后续网络请求的链是(合理)最新的。
遗憾的是,每次用户导航到新网页时都必须与网络进行交互,这意味着每次导航都可能会很慢,至少意味着导航速度不会可靠地快。
如需加快这些请求的速度,如果您能预测用户的操作,则可以提前请求这些网页和资源,并将其在缓存中保留一小段时间,直到用户点击这些链接。此技术称为预加载,通常通过向网页添加 <link rel="prefetch">
标记来实现,以指明要预加载的资源。
在本指南中,我们将探索如何将服务工件用作传统预加载技术的补充。
生产案例
MercadoLibre 是拉丁美洲最大的电子商务网站。为了加快导航速度,它们会在流程的某些部分动态注入 <link rel="prefetch">
标记。例如,在商品详情页面中,当用户滚动到商品详情的底部时,它们会立即提取下一页结果:
系统会以“最低”优先级请求预提取的文件,并将其存储在 HTTP 缓存或内存缓存(具体取决于资源是否可缓存)中,存储时间因浏览器而异。例如,从 Chrome 85 开始,此值为 5 分钟。资源会保留 5 分钟左右,之后会应用资源的常规 Cache-Control
规则。
使用 Service Worker 缓存可以将预提取资源的生命周期延长到超过五分钟窗口。
例如,意大利体育门户网站 Virgilio Sport 使用服务工件预加载其首页中最热门的帖子。它们还使用 Network Information API 来避免为使用 2G 连接的用户预加载。
因此,在 3 周的观察期内,Virgilio Sport 发现,导航到文章的加载时间缩短了 78%,文章展示次数增加了 45%。
使用 Workbox 实现预缓存
在下一部分,我们将使用 Workbox 演示如何在 Service Worker 中实现不同的缓存技术,这些技术可通过将此任务完全委托给 Service Worker,作为 <link rel="prefetch">
的补充,甚至可以替代它。
1. 预缓存静态网页和网页子资源
预缓存是指 Service Worker 在安装时将文件保存到缓存的功能。
在以下情况下,预缓存用于实现与预加载类似的目标:加快导航速度。
预缓存静态网页
对于在构建时生成的页面(例如 about.html
、contact.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: […]});
- 在 Service Worker 中:实现一个消息处理程序,为每个网址发出
fetch()
请求以进行预提取:
addEventListener('message', (event) => {
if (event.data.type === 'PREFETCH_URLS') {
// Fetch URLs and store them in the cache
}
});