默认情况下,较新的 Service Worker

tl;dr

从 Chrome 68 开始,用于检查 Service Worker 脚本更新的 HTTP 请求默认不再由 HTTP 缓存执行。这解决了开发者常见的难题:在服务工作器脚本中无意中设置 Cache-Control 标头可能会导致更新延迟。

如果您已通过使用 Cache-Control: max-age=0/service-worker.js 脚本停用 HTTP 缓存,则不会因新默认行为而看到任何变化。

此外,从 Chrome 78 开始,逐字节比较将应用于通过 importScripts() 在服务工作器中加载的脚本。对导入的脚本所做的任何更改都会触发服务工作线程更新流程,就像对顶级服务工作线程所做的更改一样。

背景

每当您导航到服务工作线程作用域下的某个新页面、从 JavaScript 显式调用 registration.update(),或者通过 pushsync 事件“唤醒”服务工作线程时,浏览器都会并行请求最初传入 navigator.serviceWorker.register() 调用的 JavaScript 资源,以查找服务工作线程脚本的更新。

在本文中,我们假设该网址为 /service-worker.js,并且包含对 importScripts() 的单次调用,该调用会加载在服务工作器内运行的其他代码:

// Inside our /service-worker.js file:
importScripts('path/to/import.js');

// Other top-level code goes here.

有何变化?

在 Chrome 68 之前,系统会通过 HTTP 缓存发出对 /service-worker.js 的更新请求(就像大多数提取操作一样)。这意味着,如果脚本最初是使用 Cache-Control: max-age=600 发送的,那么在接下来的 600 秒(10 分钟)内,系统不会将更新发送到网络,因此用户可能不会收到最新版本的服务工件。不过,如果 max-age 大于 86400(24 小时),则会被视为 86400,以免用户永远卡在特定版本上。

从 68 版开始,在请求 Service Worker 脚本更新时,系统会忽略 HTTP 缓存,因此现有 Web 应用对其 Service Worker 脚本的请求频率可能会增加。importScripts 的请求仍需经由 HTTP 缓存处理。但这只是默认行为,现在提供新的注册选项 updateViaCache,用于对此行为进行控制。

updateViaCache

开发者现在可以在调用 navigator.serviceWorker.register() 时传入一个新选项:updateViaCache 参数。 它采用以下三个值之一:'imports''all''none'

这些值决定了在发出 HTTP 请求以检查是否有更新的 Service Worker 资源时,浏览器的标准 HTTP 缓存是否会发挥作用以及如何发挥作用。

  • 设置为 'imports' 后,在检查 /service-worker.js 脚本的更新时,系统将永远不会查询 HTTP 缓存,但在提取任何导入的脚本(在我们的示例中为 path/to/import.js)时,系统会查询 HTTP 缓存。这是默认设置,与 Chrome 68 及更高版本中的行为一致。

  • 设置为 'all' 时,在对顶级 /service-worker.js 脚本以及服务 worker 中导入的任何脚本(例如 path/to/import.js)发出请求时,系统都会查询 HTTP 缓存。此选项对应于 Chrome 68 之前的 Chrome 中的旧行为。

  • 设置为 'none' 后,在对顶级 /service-worker.js 或任何导入的脚本(例如假设的 path/to/import.js)发出请求时,系统不会查询 HTTP 缓存。

例如,以下代码将注册一个 Service Worker,并确保在检查 /service-worker.js 脚本或 /service-worker.js 内通过 importScripts() 引用的任何脚本的更新时,永远不会咨询 HTTP 缓存:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js', {
    updateViaCache: 'none',
    // Optionally, set 'scope' here, if needed.
  });
}

检查导入的脚本是否有更新

在 Chrome 78 之前,通过 importScripts() 加载的任何 Service Worker 脚本都只会检索一次(先检查 HTTP 缓存,或通过网络,具体取决于 updateViaCache 配置)。初始检索后,浏览器会将其存储在内部,并且永远不会重新提取。

若要强制已安装的服务工件采用导入的脚本的更改,唯一的方法是更改脚本的网址,通常是通过添加 semver 值(例如 importScripts('https://example.com/v1.1.0/index.js'))或添加内容的哈希值(例如 importScripts('https://example.com/index.abcd1234.js'))来实现。更改导入的网址会导致顶级服务工件脚本的内容发生更改,这反过来会触发服务工件更新流程

从 Chrome 78 开始,每次对顶级服务工件文件执行更新检查时,系统都会同时进行检查,以确定是否有任何导入的脚本的内容发生了更改。如果 updateViaCache 设置为 'all''imports'(默认值),这些导入的脚本检查可能会由 HTTP 缓存执行;如果 updateViaCache 设置为 'none',这些检查可能会直接通过网络执行,具体取决于所使用的 Cache-Control 标头。

如果对导入的脚本进行更新检查后,与 Service Worker 之前存储的内容相比,字节级差异很大,这反过来会触发完整的 Service Worker 更新流程,即使顶级 Service Worker 文件保持不变也是如此。

Chrome 78 的行为与 Firefox 几年前在 Firefox 56 中实现的行为一致。Safari 也已实现此行为。

开发者需要做些什么?

如果您已通过使用 Cache-Control: max-age=0(或类似值)有效地为 /service-worker.js 脚本停用 HTTP 缓存,则您应该不会因新默认行为而看到任何变化。

如果您在启用 HTTP 缓存的情况下提供 /service-worker.js 脚本(无论是出于故意还是因为它只是托管环境的默认设置),您可能会开始看到针对服务器发出的额外 /service-worker.js HTTP 请求数量有所增加,这些请求以前是由 HTTP 缓存来处理的。如果您想继续允许 Cache-Control 标头值影响 /service-worker.js 的新鲜度,则需要在注册服务工件时开始显式设置 updateViaCache: 'all'

鉴于可能有大量用户使用旧版浏览器,因此最好继续在服务工件脚本中设置 Cache-Control: max-age=0 HTTP 标头,即使较新版本的浏览器可能会忽略这些标头也是如此。

开发者可以借此机会决定是否要立即明确选择让导入的脚本不使用 HTTP 缓存,并根据需要将 updateViaCache: 'none' 添加到其服务工件注册中。

传送导入的脚本

从 Chrome 78 开始,开发者可能会看到更多针对通过 importScripts() 加载的资源的传入 HTTP 请求,因为系统现在会检查这些资源是否有更新。

如果您想避免此类额外的 HTTP 流量,请在传送网址中包含 semver 或哈希的脚本时设置长效 Cache-Control 标头,并依赖于 'imports' 的默认 updateViaCache 行为。

或者,如果您希望系统检查导入的脚本是否有频繁更新,请确保您使用 Cache-Control: max-age=0updateViaCache: 'none' 提供这些脚本。

深入阅读

我们建议所有向 Web 部署任何内容的开发者阅读 Jake Archibald 撰写的“Service Worker 生命周期”和“缓存最佳实践和 max-age 注意事项”。