使用外掛程式

使用 Workbox 時,您可能會想在系統擷取或快取要求和回應時加以操控。透過 Workbox 外掛程式,您能以最少的樣板將其他行為新增至 Service Worker。這些套件可封裝並在您的專案中重複使用,或是公開發布供他人使用。

Workbox 提供許多立即可用的外掛程式,如果您很擅長,也可以根據應用程式的需求編寫自訂外掛程式。

可用的 Workbox 外掛程式

Workbox 提供下列官方外掛程式,供您在 Service Worker 中使用:

無論是上述其中一種外掛程式,還是自訂外掛程式,都可以與 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 外掛程式需要實作一或多個回呼函式。當您將外掛程式新增至策略時,系統會自動在適當時機執行回呼函式。策略會傳送回呼函式目前要求和/或回應的相關資訊,讓外掛程式瞭解需要採取行動的背景。系統支援下列回呼函式:

  • 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 告訴我們!