默认情况下,较新的 Service Worker

tl;dr

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

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

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

背景

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

在本文中,我们假设该网址为 /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 版开始,在请求更新服务工件脚本时,系统会忽略 HTTP 缓存,因此现有 Web 应用对其服务工件脚本的请求频率可能会增加。对 importScripts 的请求仍会通过 HTTP 缓存。但这只是默认行为,我们提供了新的注册选项 updateViaCache,可用于控制此行为。

updateViaCache

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

这些值决定了在发出 HTTP 请求以检查是否有更新的服务工件资源时,浏览器的标准 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 之前的行为。

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

例如,以下代码将注册一个服务工件,并确保在检查 /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() 加载的任何服务工件脚本都只会检索一次(先检查 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 标头。

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

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 注意事项”。