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()
,或者通过 push
或 sync
事件“唤醒”服务工作线程时,浏览器都会并行请求最初传入 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 开始,每次对顶级服务工件文件执行更新检查时,系统都会同时进行检查,以确定是否有任何导入的脚本的内容发生了变化。根据使用的 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
脚本(无论是出于故意还是因为它只是托管环境的默认设置),您可能会开始看到针对服务器发出的额外 /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=0
或 updateViaCache: 'none'
提供这些脚本。
深入阅读
我们建议所有向 Web 部署任何内容的开发者阅读 Jake Archibald 撰写的“Service Worker 生命周期”和“缓存最佳实践和 max-age 注意事项”。