立即處理 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() 就會指示等待服務工作站叫用 self.skipWaiting(),表示接下來會開始啟用。啟用後,新的 Service Worker 將負責控管任何現有用戶端,並在 workbox-window 中觸發 controlling 事件。在這種情況下,系統會使用最新版所有預快取資產,以及更新版 Service Worker 中的任何新轉送邏輯,重新載入目前的網頁。

要放入 Service Worker 的程式碼

從上一節中取得程式碼後,您必須在服務 Worker 中新增一些程式碼,讓服務工作人員知道何時可以略過等待階段。如果您使用 workbox-buildgenerateSW,且已將 skipWaiting 選項設為 false (預設),那麼沒問題,因為產生的 Service Worker 檔案會自動包含下列程式碼。

如要編寫自己的 Service Worker (可與其中一個 Workbox 建構工具在 injectManifest 模式下使用),必須先自行新增下列程式碼:

addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

這將會監聽由 workbox-window 傳送至服務工作站的訊息 (type 值為 SKIP_WAITING),並在呼叫 self.skipWaiting() 時呼叫 self.skipWaiting()workbox-window 中的 messageSkipWaiting() 方法 (如先前的程式碼範例所示) 負責傳送這則訊息。

您需要顯示提示嗎?

這不是每個部署 Service Worker 必須遵循的模式。在特定情況下,如果您未能在服務工作人員更新時重新載入頁面,可能造成非預期的行為。至於是否該顯示重新載入提示,並沒有硬性規定,以下列出一些可能適合您的情況:

  • 廣泛使用預先快取。對於靜態資產,如果使用以網路優先或僅限網路的策略處理導覽要求,但使用延遲載入的靜態資產,則可能會發生問題。這可能會導致版本化資產發生異動,且 Service Worker 尚未預先快取這些資產。在這裡提供重新載入按鈕,可避免發生非預期的行為。
  • 您是否要提供預先快取的 HTML。在這種情況下,請「強烈」考慮在 Service Worker 更新時提供重新載入按鈕,因為只有在更新後的 Service Worker 主受更新後,系統才會辨識 HTML 的更新。
  • 如果您主要不是仰賴執行階段快取,在執行階段快取資源時,您不需要告知使用者應重新載入資源。版本化資產變更之後,系統會在一段時間後將這些資產加入執行階段快取,假設導覽要求採用「以網路為優先」或「僅限網路」策略。
  • 使用「過時的重新驗證」策略時,可考慮使用 workbox-broadcast-update 模組通知使用者有關 Service Worker 的更新資訊。

您是否需要通知使用者更新的服務工作處理程序,取決於您的應用程式及其獨特需求。如果您發現在推送新的 Service Worker 時使用者遇到異常行為,這可能就是您應該通知使用者的最佳信號。