使用外掛程式

使用 Workbox 時,您可能會想在擷取或快取時操控要求和回應。您可以使用 Workbox 外掛程式,在服務工作站中加入其他行為,而不需要額外撰寫大量程式碼。您可以將這些程式碼打包,在自己的專案中重複使用,也可以公開發布,供其他人使用。

Workbox 提供多種外掛程式,可供我們立即使用。如果您是技術高手,可以編寫自訂外掛程式,以符合應用程式需求。

可用的 Workbox 外掛程式

Workbox 提供下列官方外掛程式,可用於服務工作程式:

Workbox 外掛程式 (無論是上述外掛程式,或自訂外掛程式) 要與 Workbox 策略搭配使用,方法是將外掛程式執行個體新增至策略的 plugins 屬性

import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
import {ExpirationPlugin} from 'workbox-expiration';

registerRoute(
  ({request}) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 60,
        maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
      }),
    ],
  })
);

自訂外掛程式的方法

Workbox 外掛程式需要實作一或多個回呼函式。在策略中加入外掛程式後,系統會在適當時間自動執行回呼函式。Strategy 會將目前要求和/或回應的相關資訊傳遞至回呼函式,讓外掛程式取得執行動作所需的背景資訊。支援的回呼函式如下:

  • cacheWillUpdate:在使用 Response 更新快取之前呼叫。在這個方法中,您可以在回應加入快取前變更回應,或是傳回 null 來避免全面更新快取。
  • cacheDidUpdate:在快取中新增項目或更新現有項目時,會呼叫此方法。如果您想在快取更新後執行動作,使用這個方法的外掛程式可能就很實用。
  • cacheKeyWillBeUsed:在要求用於做為快取鍵之前呼叫。這適用於快取查詢 (當 mode'read') 和快取寫入 (當 mode'write' 時)。如果您在使用網址存取快取之前需要覆寫或正規化網址,這個回呼就非常實用。
  • cachedResponseWillBeUsed:在使用快取回應前呼叫,方便您檢查該回應。這時您可以傳回不同的回應,或是傳回 null
  • requestWillFetch:每當要求即將傳送至網路時,就會呼叫此方法。在將 Request 傳送至網路前,需要變更時非常實用。
  • fetchDidFail:在網路要求失敗時呼叫,這通常是因為沒有網路連線,且在瀏覽器有網路連線但收到錯誤 (例如 404 Not Found) 時不會觸發。
  • fetchDidSucceed:無論 HTTP 回應代碼為何,只要網路要求成功,就會呼叫此方法。
  • handlerWillStart:在任何處理常式邏輯開始執行前呼叫,如果您需要設定初始處理常式狀態,這項作業就很實用。舉例來說,如果您想瞭解處理常式產生回應所需的時間,可以在這個回呼中記下開始時間。
  • handlerWillRespond:在策略的 handle() 方法傳回回應之前呼叫,如果需要先修改回應再傳回 RouteHandler 或其他自訂邏輯,這項功能就非常實用。
  • handlerDidRespond:在策略的 handle() 方法 傳回回應後呼叫。這時記錄最終回應詳細資料可能會很有幫助 (例如在其他外掛程式變更後)。
  • handlerDidComplete:在策略呼叫時新增至事件的所有延長生命週期承諾已完成後,系統會呼叫此方法。如果您需要回報需等待處理常式完成的資料,以便系統計算快取命中狀態、快取延遲時間、網路延遲及其他實用資訊等相關資料,這項功能就非常實用。
  • handlerDidError:如果處理常式無法從「任何」提供有效回應,就會呼叫此呼叫,這是提供某種備用回應的最佳時機,而這是避免非完全錯誤的情況。

所有這些回呼都是 async,因此當快取或擷取事件到達相關回呼的相關位置時,就必須使用 await

如果外掛程式使用上述所有回呼,就會產生以下程式碼:

const myPlugin = {
  cacheWillUpdate: async ({request, response, event, state}) => {
    // Return `response`, a different `Response` object, or `null`.
    return response;
  },
  cacheDidUpdate: async ({
    cacheName,
    request,
    oldResponse,
    newResponse,
    event,
    state,
  }) => {
    // No return expected
    // Note: `newResponse.bodyUsed` is `true` when this is called,
    // meaning the body has already been read. If you need access to
    // the body of the fresh response, use a technique like:
    // const freshResponse = await caches.match(request, {cacheName});
  },
  cacheKeyWillBeUsed: async ({request, mode, params, event, state}) => {
    // `request` is the `Request` object that would otherwise be used as the cache key.
    // `mode` is either 'read' or 'write'.
    // Return either a string, or a `Request` whose `url` property will be used as the cache key.
    // Returning the original `request` will make this a no-op.
    return request;
  },
  cachedResponseWillBeUsed: async ({
    cacheName,
    request,
    matchOptions,
    cachedResponse,
    event,
    state,
  }) => {
    // Return `cachedResponse`, a different `Response` object, or null.
    return cachedResponse;
  },
  requestWillFetch: async ({request, event, state}) => {
    // Return `request` or a different `Request` object.
    return request;
  },
  fetchDidFail: async ({originalRequest, request, error, event, state}) => {
    // No return expected.
    // Note: `originalRequest` is the browser's request, `request` is the
    // request after being passed through plugins with
    // `requestWillFetch` callbacks, and `error` is the exception that caused
    // the underlying `fetch()` to fail.
  },
  fetchDidSucceed: async ({request, response, event, state}) => {
    // Return `response` to use the network response as-is,
    // or alternatively create and return a new `Response` object.
    return response;
  },
  handlerWillStart: async ({request, event, state}) => {
    // No return expected.
    // Can set initial handler state here.
  },
  handlerWillRespond: async ({request, response, event, state}) => {
    // Return `response` or a different `Response` object.
    return response;
  },
  handlerDidRespond: async ({request, response, event, state}) => {
    // No return expected.
    // Can record final response details here.
  },
  handlerDidComplete: async ({request, response, error, event, state}) => {
    // No return expected.
    // Can report any data here.
  },
  handlerDidError: async ({request, event, error, state}) => {
    // Return a `Response` to use as a fallback, or `null`.
    return fallbackResponse;
  },
};

上述回呼中提供的 event 物件,是觸發擷取或快取動作的原始事件。有時不會有原始事件,因此程式碼應先檢查原始事件是否存在,再參照該事件。

所有外掛程式回呼也會傳遞 state 物件,該物件是特定外掛程式和其叫用策略的專屬物件。也就是說,您可以編寫外掛程式,讓其中一個回呼可以根據同個外掛程式中的另一個回呼所執行的操作,有條件地執行工作 (例如,計算執行 requestWillFetch()fetchDidSucceed()fetchDidFail() 之間的差異)。

第三方外掛程式

如果您開發了外掛程式,並認為它可在專案之外使用,建議您以模組形式發布該外掛程式!以下是社群提供的 Workbox 外掛程式清單:

您可以透過 npm 存放區搜尋,找出更多社群提供的 Workbox 外掛程式

最後,如果您已建構要分享的 Workbox 外掛程式,請在發布時新增 workbox-plugin 關鍵字。如果有,歡迎在 Twitter 上告訴我們 @WorkboxJS