在运行时缓存资源

您的 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 实例中。

在以下示例中,图片使用 stale-while-revalidate 策略,而 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 开发者工具“应用”面板中的缓存存储空间查看器。不同资源类型的响应存储在不同的缓存中。

为缓存条目设置过期时间

管理 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 兆字节。在确定要缓存多少不透明响应时,您应牢记这一点,因为您可能会比预期要快得多,超出存储空间配额。