在运行时缓存资源

您的 Web 应用中的一些资源可能不经常使用、非常大,或者会因用户的设备(例如自适应图片)或语言而异。在这类情况下,预缓存可能是一种反模式,您应改为依赖运行时缓存。

在 Workbox 中,您可以使用 workbox-routing 模块处理资源的运行时缓存以匹配路由,并使用 workbox-strategies 模块处理这些资源的缓存策略。

缓存策略

您可以使用其中一种内置缓存策略来处理资产的大多数路由。本文档前面已对这些内容进行了详细介绍,但下面几个要点值得回顾:

  • 过时期间重新验证会针对请求使用缓存的响应(如果有),并使用来自网络的响应在后台更新缓存。因此,如果资源未缓存,它将等待网络响应并使用它。这是一种相当安全的策略,因为它会定期更新依赖于它的缓存条目。其缺点是始终在后台向网络请求资源。
  • 网络优先:尝试首先从网络获取响应。如果收到响应,则会将该响应传递给浏览器,并将其保存到缓存。如果网络请求失败,将使用上次缓存的响应,实现资产的离线访问。
  • 缓存优先:首先检查缓存是否有响应,如果有响应,则使用该缓存。如果请求不在缓存中,则系统会使用网络,并在传递到浏览器之前将任何有效响应添加到缓存中。
  • 仅网络会强制响应来自网络。
  • 仅缓存会强制响应来自缓存。

您可以使用 workbox-routing 提供的方法将这些策略应用于选择请求。

通过路由匹配应用缓存策略

workbox-routing 公开了 registerRoute 方法,用于匹配路由并使用缓存策略处理路由。registerRoute 接受 Route 对象,而该对象又接受两个参数:

  1. 用于指定路线匹配条件的字符串、正则表达式匹配回调
  2. 路线的处理程序 - 通常是由 workbox-strategies 提供的策略。

匹配回调首选匹配路由,因为它们提供的上下文对象包含 Request 对象、请求网址字符串、提取事件以及表示请求是否为同源请求的布尔值。

然后,处理程序会处理匹配的路由。在以下示例中,系统会创建一个与传入的同源图片请求相匹配的新路由,该路由首先应用缓存,然后回退到网络策略

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';

// A new route that matches same-origin image requests and handles
// them with the cache-first, falling back to network strategy:
const imageRoute = new Route(({ request, sameOrigin }) => {
  return sameOrigin && request.destination === 'image'
}, new CacheFirst());

// Register the new route
registerRoute(imageRoute);

使用多个缓存

借助 Workbox,您可以使用捆绑策略中提供的 cacheName 选项将缓存的响应分桶到单独的 Cache 实例中。

在以下示例中,图片使用“过时重新验证”策略,而 CSS 和 JavaScript 资源使用缓存优先回退到网络策略。每个资源的路由通过添加 cacheName 属性,将响应放入单独的缓存中。

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';

// Handle images:
const imageRoute = new Route(({ request }) => {
  return request.destination === 'image'
}, new StaleWhileRevalidate({
  cacheName: 'images'
}));

// Handle scripts:
const scriptsRoute = new Route(({ request }) => {
  return request.destination === 'script';
}, new CacheFirst({
  cacheName: 'scripts'
}));

// Handle styles:
const stylesRoute = new Route(({ request }) => {
  return request.destination === 'style';
}, new CacheFirst({
  cacheName: 'styles'
}));

// Register routes
registerRoute(imageRoute);
registerRoute(scriptsRoute);
registerRoute(stylesRoute);
Chrome 开发者工具“应用”标签页中一系列缓存实例的屏幕截图。这里显示了三个不同的缓存:一个名为“scripts”,另一个名为“styles”,最后一个名为“images”。
Chrome 开发者工具“Application”面板中的 Cache storage 查看器。不同资产类型的响应存储在单独的缓存中。

设置缓存条目的过期时间

管理 Service Worker 缓存时,请注意存储空间配额。ExpirationPlugin 可简化缓存维护,并由 workbox-expiration 公开。要使用它,请在缓存策略的配置中指定它:

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';

// Evict image cache entries older thirty days:
const imageRoute = new Route(({ request }) => {
  return request.destination === 'image';
}, new CacheFirst({
  cacheName: 'images',
  plugins: [
    new ExpirationPlugin({
      maxAgeSeconds: 60 * 60 * 24 * 30,
    })
  ]
}));

// Evict the least-used script cache entries when
// the cache has more than 50 entries:
const scriptsRoute = new Route(({ request }) => {
  return request.destination === 'script';
}, new CacheFirst({
  cacheName: 'scripts',
  plugins: [
    new ExpirationPlugin({
      maxEntries: 50,
    })
  ]
}));

// Register routes
registerRoute(imageRoute);
registerRoute(scriptsRoute);

遵守存储空间配额并非易事。最好考虑那些可能正面临存储空间压力或希望最有效地利用存储空间的用户。Workbox 的 ExpirationPlugin 对有助于实现该目标。

跨源注意事项

Service Worker 和跨源资源之间的交互与同源资源之间的交互明显不同。跨域资源共享 (CORS) 非常复杂,在 Service Worker 中处理跨源资源的方式也非常复杂。

不透明响应

no-cors 模式下发出跨源请求时,响应可存储在 Service Worker 缓存中,甚至直接由浏览器使用。但是,响应正文本身不能通过 JavaScript 读取。这称为不透明响应

不透明响应是一种用于防止检查跨源资产的安全措施。您仍然可以请求跨源资源,甚至缓存这些资源,只是无法读取响应正文,甚至无法读取其状态代码

记得选择启用 CORS 模式

即使您加载的跨源资源确实设置了宽松的 CORS 标头来允许您读取响应,跨源响应的正文可能仍然是不透明的。例如,以下 HTML 会触发 no-cors 请求,从而导致响应不透明,无论设置何种 CORS 标头:

<link rel="stylesheet" href="https://example.com/path/to/style.css">
<img src="https://example.com/path/to/image.png">

要显式触发会生成非不透明响应的 cors 请求,您需要向 HTML 添加 crossorigin 属性,从而明确选择启用 CORS 模式:

<link crossorigin="anonymous" rel="stylesheet" href="https://example.com/path/to/style.css">
<img crossorigin="anonymous" src="https://example.com/path/to/image.png">

当 Service Worker 中的路由在运行时缓存子资源时,请务必记住这一点。

Workbox 不能缓存不透明响应

默认情况下,Workbox 会谨慎地缓存不透明响应。由于无法检查响应代码是否存在不透明响应,因此如果使用缓存优先或仅缓存策略,缓存错误响应可能会导致体验持续中断。

如果您需要在 Workbox 中缓存不透明响应,则应使用网络优先或过时验证策略来处理。会。这意味着系统仍然每次都会从网络请求该资源,但可以确保失败的响应不会持续存在,而最终会替换为可用的响应。

如果您使用其他缓存策略并且返回了不透明的响应,Workbox 将警告您,在开发模式下未缓存响应。

强制缓存不透明响应

如果您绝对确定想要使用缓存优先或仅缓存策略来缓存不透明响应,则可以使用 workbox-cacheable-response 模块强制 Workbox 执行此操作:

import {Route, registerRoute} from 'workbox-routing';
import {NetworkFirst, StaleWhileRevalidate} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response';

const cdnRoute = new Route(({url}) => {
  return url === 'https://cdn.google.com/example-script.min.js';
}, new CacheFirst({
  plugins: [
    new CacheableResponsePlugin({
      statuses: [0, 200]
    })
  ]
}))

registerRoute(cdnRoute);

不透明响应和 navigator.storage API

为避免跨网域信息泄露,在用于计算存储空间配额限制的不透明响应的大小中添加了大量填充。这会影响 navigator.storage API 报告存储空间配额的方式。

此内边距因浏览器而异,但对于 Chrome 来说,任何一个缓存的不透明响应在占用的总存储空间中所占的最小大小为大约 7 MB。在确定要缓存的不透明响应数量时,您应牢记这一点,因为您可能会比您预期的要快得多超出存储配额。