Workbox を使用した復元性に優れた検索エクスペリエンスの構築

この Codelab では、Workbox で復元性に優れた検索エクスペリエンスを実装する方法について説明します。使用するデモアプリには検索ボックスがあり、サーバー エンドポイントを呼び出して、ユーザーを基本的な HTML ページにリダイレクトします。

測定

最適化を追加する前に、まずアプリケーションの現在の状態を分析することをおすすめします。

  • [Remix to Edit] をクリックして、プロジェクトを編集可能にします。
  • サイトをプレビューするには、[アプリを表示] を押します。[ 全画面表示 全画面表示

開いたばかりの新しいタブで、オフライン時のウェブサイトの動作を確認します。

  1. Ctrl+Shift+J キー(Mac の場合は Command+Option+J キー)を押して DevTools を開きます。
  2. [ネットワーク] タブをクリックします。
  3. Chrome DevTools を開き、[ネットワーク] パネルを選択します。
  4. [スロットリング] プルダウン リストで、[オフライン] を選択します。
  5. デモアプリで検索クエリを入力して、[Search] ボタンをクリックします。

標準のブラウザのエラーページが表示されます。

ブラウザのデフォルトのオフライン UX のスクリーンショット。

フォールバック レスポンスを提供する

Service Worker には、オフライン ページをプレキャッシュ リストに追加するコードが含まれているため、Service Worker の install イベントで常にキャッシュに保存できます。

通常は、任意のビルドツール(webpackgulp など)にライブラリを統合することで、ビルド時にこのファイルをプレキャッシュ リストに追加するように Workbox に指示する必要があります。

わかりやすくするために、すでに完了しています。そのために、次のコード(public/sw.js)を使用します。

const FALLBACK_HTML_URL = '/index_offline.html';

workbox.precaching.precacheAndRoute([FALLBACK_HTML_URL]);

次に、オフライン ページをフォールバック レスポンスとして使用するコードを追加します。

  1. ソースを表示するには、[View Source] を押します。
  2. public/sw.js の末尾に次のコードを追加します。
workbox.routing.setDefaultHandler(new workbox.strategies.NetworkOnly());

workbox.routing.setCatchHandler(({event}) => {
  switch (event.request.destination) {
    case 'document':
      return caches.match(FALLBACK_HTML_URL);
      break;
    default:
      return Response.error();
  }
});

コードは次の処理を行います。

  • すべてのリクエストに適用されるデフォルトのネットワークのみの戦略を定義します。
  • workbox.routing.setCatchHandler() を呼び出して失敗したリクエストを管理することにより、グローバル エラーハンドラを宣言します。ドキュメントに対するリクエストの場合は、代替のオフライン HTML ページが返されます。

この機能をテストする手順は次のとおりです。

  1. アプリを実行している別のタブに戻ります。
  2. [スロットリング] プルダウン リストを [オンライン] に戻します。
  3. Chrome の [戻る] ボタンを押して検索ページに戻ります。
  4. DevTools の [キャッシュを無効にする] チェックボックスがオフになっていることを確認します。
  5. Chrome の [再読み込み] ボタンを長押しして、 キャッシュを空にしてハードリロード Service Worker が確実に更新されるようにします。
  6. [スロットリング] プルダウン リストを [オフライン] に戻します。
  7. 検索クエリを入力し、もう一度 [検索] ボタンをクリックします。

次の代替 HTML ページが表示されます。

ブラウザ上のカスタムのオフライン UX のスクリーンショット。

通知権限をリクエストする

わかりやすくするため、views/index_offline.html のオフライン ページの一番下のスクリプト ブロックには、通知権限をリクエストするコードがすでに含まれています。

function requestNotificationPermission(event) {
  event.preventDefault();

  Notification.requestPermission().then(function (result) {
    showOfflineText(result);
  });
}

コードは次の処理を行います。

  • ユーザーが [通知に登録] をクリックすると、requestNotificationPermission() 関数が呼び出されます。この関数は Notification.requestPermission() を呼び出し、デフォルトのブラウザ権限プロンプトを表示します。Promise はユーザーが選択した権限(granteddenieddefault のいずれか)で解決されます。
  • 適切なテキストをユーザーに表示するために、解決済みの権限を showOfflineText() に渡します。

オフライン クエリを保持し、オンラインに戻ったときに再試行する

次に、Workbox Background Sync を実装してオフライン クエリを保持し、接続が回復したことをブラウザが検出したときにクエリを再試行できるようにします。

  1. public/sw.js を編集用に開きます。
  2. ファイルの末尾に次のコードを追加します。
const bgSyncPlugin = new workbox.backgroundSync.Plugin('offlineQueryQueue', {
  maxRetentionTime: 60,
  onSync: async ({queue}) => {
    let entry;
    while ((entry = await queue.shiftRequest())) {
      try {
        const response = await fetch(entry.request);
        const cache = await caches.open('offline-search-responses');
        const offlineUrl = `${entry.request.url}&notification=true`;
        cache.put(offlineUrl, response);
        showNotification(offlineUrl);
      } catch (error) {
        await this.unshiftRequest(entry);
        throw error;
      }
    }
  },
});

コードは次の処理を行います。

  • workbox.backgroundSync.Plugin には、失敗したリクエストをキューに追加して、後で再試行できるようにするロジックが含まれています。これらのリクエストは IndexedDB に保持されます。
  • maxRetentionTime は、リクエストを再試行できる時間を示します。ここでは、60 分を選択しました(60 分が経過すると破棄されます)。
  • onSync は、このコードの最も重要な部分です。接続が回復したときにこのコールバックが呼び出され、キューに入れられたリクエストがネットワークから取得され、フェッチされます。
  • ネットワーク レスポンスは offline-search-responses キャッシュに追加され、&notification=true クエリ パラメータが追加されます。これにより、ユーザーが通知をクリックしたときにこのキャッシュ エントリを取得できるようになります。

バックグラウンド同期をサービスと統合するには、検索 URL(/search_action)へのリクエストに NetworkOnly 戦略を定義し、以前に定義した bgSyncPlugin を渡します。public/sw.js の末尾に次のコードを追加します。

const matchSearchUrl = ({url}) => {
  const notificationParam = url.searchParams.get('notification');
  return url.pathname === '/search_action' && !(notificationParam === 'true');
};

workbox.routing.registerRoute(
  matchSearchUrl,
  new workbox.strategies.NetworkOnly({
    plugins: [bgSyncPlugin],
  }),
);

これにより、常にネットワークに接続し、リクエストが失敗した場合にバックグラウンド同期ロジックを使用するように Workbox に指示されます。

次に、以下のコードを public/sw.js の下部に追加して、通知からのリクエストに対するキャッシュ戦略を定義します。CacheFirst 戦略を使用して、キャッシュから配信できるようにします。

const matchNotificationUrl = ({url}) => {
  const notificationParam = url.searchParams.get('notification');
  return (url.pathname === '/search_action' && (notificationParam === 'true'));
};

workbox.routing.registerRoute(matchNotificationUrl,
  new workbox.strategies.CacheFirst({
     cacheName: 'offline-search-responses',
  })
);

最後に、通知を表示するコードを追加します。

function showNotification(notificationUrl) {
  if (Notification.permission) {
     self.registration.showNotification('Your search is ready!', {
        body: 'Click to see you search result',
        icon: '/img/workbox.jpg',
        data: {
           url: notificationUrl
        }
     });
  }
}

self.addEventListener('notificationclick', function(event) {
  event.notification.close();
  event.waitUntil(
     clients.openWindow(event.notification.data.url)
  );
});

機能をテストする

  1. アプリを実行している別のタブに戻ります。
  2. [スロットリング] プルダウン リストを [オンライン] に戻します。
  3. Chrome の [戻る] ボタンを押して検索ページに戻ります。
  4. Chrome の [再読み込み] ボタンを長押しして、 キャッシュを空にしてハードリロード Service Worker が確実に更新されるようにします。
  5. [スロットリング] プルダウン リストを [オフライン] に戻します。
  6. 検索クエリを入力し、もう一度 [検索] ボタンをクリックします。
  7. [通知を受け取る] をクリックします。
  8. 通知を送信する権限をアプリに付与するかどうかを尋ねられたら、 [許可] をクリックします。
  9. 別の検索クエリを入力し、もう一度 [検索] ボタンをクリックします。
  10. [スロットリング] プルダウン リストを [オンライン] に戻します。

接続が回復すると、通知が表示されます。

完全なオフライン フローのスクリーンショット。

まとめ

Workbox には、PWA の復元性と魅力を高める機能が数多く組み込まれています。 この Codelab では、Workbox の抽象化によって Background Sync API を実装し、オフラインのユーザークエリが失われず、接続が復旧した後に再試行できるようにする方法を確認しました。 このデモはシンプルな検索アプリですが、チャットアプリ、ソーシャル ネットワークへのメッセージ投稿など、より複雑なシナリオやユースケースにも同様の実装を使用できます。