默认情况下,Service Worker 生命周期要求在找到并安装更新的 Service Worker 时,当前 Service Worker 控制的所有打开的标签页必须先关闭或进行导航,然后才能激活并控制更新后的 Service Worker。
在许多情况下,在适当的时候允许此操作是可以的,但在某些情况下,您可能需要提前告知用户有待处理的 Service Worker 更新,然后自动切换到新 Service Worker 的过程。为此,您需要在页面和 Service Worker 中添加一些代码。
要添加到您网页上的代码
以下代码使用从 CDN 托管的 workbox-window
版本导入的 JavaScript 模块,在内嵌 <script>
元素中运行。它使用 workbox-window
注册 Service Worker,并在 Service Worker 卡在等待阶段时做出反应。当发现等待的 Service Worker 时,此代码会通知用户网站的更新版本可用,并提示他们重新加载。
<!-- This script tag uses JavaScript modules, so the proper `type` attribute value is required -->
<script type="module">
// This code sample uses features introduced in Workbox v6.
import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
let registration;
const showSkipWaitingPrompt = async (event) => {
// Assuming the user accepted the update, set up a listener
// that will reload the page as soon as the previously waiting
// service worker has taken control.
wb.addEventListener('controlling', () => {
// At this point, reloading will ensure that the current
// tab is loaded under the control of the new service worker.
// Depending on your web app, you may want to auto-save or
// persist transient state before triggering the reload.
window.location.reload();
});
// When `event.wasWaitingBeforeRegister` is true, a previously
// updated service worker is still waiting.
// You may want to customize the UI prompt accordingly.
// This code assumes your app has a promptForUpdate() method,
// which returns true if the user wants to update.
// Implementing this is app-specific; some examples are:
// https://open-ui.org/components/alert.research or
// https://open-ui.org/components/toast.research
const updateAccepted = await promptForUpdate();
if (updateAccepted) {
wb.messageSkipWaiting();
}
};
// Add an event listener to detect when the registered
// service worker has installed but is waiting to activate.
wb.addEventListener('waiting', (event) => {
showSkipWaitingPrompt(event);
});
wb.register();
}
</script>
如果它们接受,messageSkipWaiting()
会告知等待的 Service Worker 调用 self.skipWaiting()
,这意味着它将开始激活。激活后,新 Service Worker 将控制所有现有客户端,从而触发 workbox-window
中的 controlling
事件。发生这种情况时,当前页面会使用所有预缓存资源的最新版本以及更新后的 Service Worker 中找到的任何新路由逻辑重新加载。
要放入 Service Worker 的代码
从页面获取上一部分中的代码后,您需要向 Service Worker 添加一些代码,使其知道何时跳过等待阶段。如果您使用的是 workbox-build
中的 generateSW
,并且将其 skipWaiting
选项设置为 false
(默认值),就可以了,因为以下代码将自动包含在生成的 Service Worker 文件中。
如果您正在编写自己的 Service Worker(也许是在 injectManifest
模式下与 Workbox 的某个构建工具结合使用),则需要自行添加以下代码:
addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
这将监听从 workbox-window
发送到 Service Worker 且 type
值为 SKIP_WAITING
的消息,当发生这种情况时,调用 self.skipWaiting()
。workbox-window
中的 messageSkipWaiting()
方法(如之前的代码示例所示)负责发送此消息。
您是否需要显示提示?
这不是每个部署 Service Worker 的应用都需要遵循的模式。此 API 适用于在 Service Worker 更新时未提供重新加载页面可能会导致意外行为的特定场景。对于是否应显示重新加载提示并没有硬性规定,但在以下几种情况中,应该显示重新加载提示是合理的:
- 您广泛使用了预缓存。如果涉及静态资源,如果您为导航请求使用网络优先或网络专用策略,但对静态资源采用延迟加载策略,则稍后可能会出现问题。这可能导致版本化资产可能发生变化,并且 Service Worker 尚未预缓存它们。在此处提供重新加载按钮可以避免一些意外行为。
- 如果您要提供预缓存的 HTML。在这种情况下,您应强烈考虑在 Service Worker 更新时提供重新加载按钮,因为只有在更新的 Service Worker 取得控制权之后,系统才会识别对该 HTML 的更新。
- 如果您不主要依赖于运行时缓存。在运行时缓存资源时,您无需告知用户应重新加载。随着版本化资产的变化,它们会在适当情况下添加到运行时缓存中(假设导航请求使用网络优先或网络专用策略)。
- 使用 stale-while-revalidate 策略时,您可以考虑使用
workbox-broadcast-update
模块通知用户 Service Worker 的更新。
是否需要通知用户 Service Worker 的更新取决于您的应用及其独特要求。如果您发现用户在推送新 Service Worker 时遇到奇怪行为,这可能是您应该通知他们的最佳信号。