利用 Service Worker 补充传统的预提取技术。
在网站上执行任务通常涉及几个步骤。例如,在电子商务网站上购买产品可能涉及搜索产品、从结果列表中选择产品、将产品添加到购物车,以及通过结账来完成此操作。
从技术角度来说,浏览不同页面意味着发出导航请求。一般来说,您不能使用长期存在的 Cache-Control
标头来缓存导航请求的 HTML 响应。一般情况下,应使用 Cache-Control: no-cache
通过网络满足这些条件,以确保 HTML 以及后续网络请求的链是(合理)最新的。
遗憾的是,用户每次导航到新网页时都必须与网络进行连接,这意味着每次导航都可能会很慢,至少也意味着速度不会“可靠”。
为了加快这些请求的处理速度,如果您能预见到用户的操作,就可以事先请求这些网页和资产,并将其在缓存中保留一小段时间,直到用户点击这些链接为止。这种技术称为“预提取”,通常通过向网页添加 <link rel="prefetch">
标记来指示要预提取的资源来实现。prefetching
在本指南中,我们将探索将 Service Worker 用作传统预提取技术的补充的不同方式。
生产案例
MercadoLibre 是拉丁美洲最大的电子商务网站。为了加快导航速度,它们会在流程的某些部分动态注入 <link rel="prefetch">
标记。例如,在商品详情页面中,当用户滚动到商品详情底部时,它们会立即获取下一个结果页:
系统会按照“最低”请求预提取文件优先级并存储在 HTTP 缓存或内存缓存中(具体取决于资源是否可缓存),时长因浏览器而异。例如,从 Chrome 85 开始,此值为 5 分钟。资源会保留 5 分钟左右,之后会应用资源的常规 Cache-Control
规则。
使用 Service Worker 缓存可以将预提取资源的生命周期延长到超过五分钟窗口。
例如,意大利体育门户网站 Virgilio Sport 使用 Service Worker 预提取首页上最热门的帖子。它们还使用 Network Information API 来避免为使用 2G 连接的用户进行预提取。
因此,Virgilio Sport 在 3 周多时间的观察中发现,浏览文章的加载时间缩短了 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 分钟。
通过 Service Worker,您可以延长预提取页面的生命周期,同时还能让这些资源可供离线使用。
在前面的示例中,您可以使用 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
}),
],
});
在本例中,我们选择使用 stale-while-revalidate 策略。在此策略中,可以同时从缓存和网络请求页面。响应来自缓存(如果有),否则来自网络。对于每个成功的请求,缓存会始终与网络响应保持同步。
3. 将预提取委托给 Service Worker
在大多数情况下,最好的方法是使用 <link rel="prefetch">
。该标记是一种资源提示,旨在使预提取尽可能高效。
但在某些情况下,最好将此任务完全委托给 Service Worker。
例如:如需在客户端呈现的商品详情页面中预提取前几个商品,可能需要根据 API 响应在页面中动态注入多个 <link rel="prefetch">
标记。这可能会暂时占用页面主线程的时间,并增加实现难度。
在这种情况下,请使用“页面对 Service Worker 通信策略”,将预提取任务完全委托给 Service Worker。此类通信可以通过使用 worker.postMessage() 来实现:
Workbox Window 软件包可简化此类通信,抽象化所完成的底层调用的许多细节。
可通过以下方式实现使用 Workbox 窗口进行预提取:
- 在页面中:调用 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
}
});