默认情况下,较新的 Service Worker

杰夫·波斯尼克
Jeff Posnick

tl;dr

从 Chrome 68 开始,默认情况下,检查 Service Worker 脚本更新的 HTTP 请求将不再由 HTTP 缓存执行。这解决了一个常见的开发者痛点,即在 Service Worker 脚本上无意中设置 Cache-Control 标头可能会导致更新延迟。

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

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

背景

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

在本文中,我们假设它的网址是 /service-worker.js,并且其中包含对 importScripts() 的单次调用,调用时会加载在 Service Worker 中运行的其他代码:

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

// Other top-level code goes here.

有何变化?

在 Chrome 68 之前,/service-worker.js 的更新请求将通过 HTTP 缓存发出(与大多数提取一样)。这意味着,如果脚本最初是使用 Cache-Control: max-age=600 发送的,则接下来 600 秒(10 分钟)内的更新将不会传输到网络,因此用户可能不会收到最新版本的 Service Worker。不过,如果 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)。这是默认设置,与从 Chrome 68 开始的行为一致。

  • 如果设置为 'all',则在为顶级 /service-worker.js 脚本以及在 Service 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 配置)。初始检索之后,浏览器将存储在内部,并且永远不会重新获取该索引。

强制已安装的 Service Worker 获取对已导入脚本的更改的唯一方法是更改脚本的网址,通常通过添加 semver 值(例如 importScripts('https://example.com/v1.1.0/index.js'))或添加内容的哈希值(例如 importScripts('https://example.com/index.abcd1234.js'))来实现。更改导入网址的附带效应是顶级 Service Worker 脚本的内容更改,这反过来又会触发

从 Chrome 78 开始,每次对顶级 Service Worker 文件执行更新检查时,系统也会同时进行检查,以确定任何导入脚本的内容是否发生了变化。根据所用的 Cache-Control 标头,如果 updateViaCache 设置为 'all''imports'(默认值),这些导入的脚本检查可能由 HTTP 缓存完成;如果 updateViaCache 设置为 'none',这些检查可能会直接针对网络执行。

如果对导入的脚本进行更新检查导致与 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 脚本时启用了 HTTP 缓存(无论是有意还是因为这是托管环境的默认配置),您可能会看到针对您的服务器发出的 /service-worker.js 额外 HTTP 请求(这些请求以前由 HTTP 缓存执行)开始上升。如果您希望继续允许 Cache-Control 标头值影响 /service-worker.js 的新鲜度,则需要在注册 Service Worker 时开始明确设置 updateViaCache: 'all'

考虑到可能有一个长尾用户使用旧版浏览器,我们仍继续在 Service Worker 脚本上设置 Cache-Control: max-age=0 HTTP 标头,即使新版浏览器可能会忽略这些标头也是如此。

开发者可以借此机会决定是否要立即明确地为导入的脚本停用 HTTP 缓存,并在适当的情况下将 updateViaCache: 'none' 添加到其 Service Worker 注册中。

传送导入的脚本

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

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

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

深入阅读

Jake Archibald 撰写的“Service Worker 生命周期”和“缓存最佳实践和 max-age 问题”是所有向 Web 部署任何内容的开发者都推荐的阅读资源。