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

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

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

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

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

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

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

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

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

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

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

わかりやすくするため、すでに設定済みです。public/sw.js の次のコードがその処理を行います。

const FALLBACK_HTML_URL = '/index_offline.html';

workbox.precaching.precacheAndRoute([FALLBACK_HTML_URL]);

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

  1. ソースを表示するには、[ソースを表示] を押します。
  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 の [Disable cache] チェックボックスが無効になっていることを確認します。
  5. Chrome の [再読み込み] ボタンを長押しし、[キャッシュを空にしてハードリロード] を選択して、サービス ワーカーが更新されていることを確認します。
  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 バックグラウンド同期を実装してオフライン クエリを保持し、ブラウザが接続が復元されたことを検出したときに再試行できるようにします。

  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 の [再読み込み] ボタンを長押しし、[キャッシュを空にしてハードリロード] を選択して、サービス ワーカーが更新されていることを確認します。
  5. [スロットリング] プルダウン リストを [オフライン] に戻します。
  6. 検索キーワードを入力し、[検索] ボタンをもう一度クリックします。
  7. [通知を設定] をクリックします。
  8. 通知を送信する権限をアプリに付与するかどうかを Chrome から尋ねられたら、[許可] をクリックします。
  9. 別の検索キーワードを入力し、[検索] ボタンをもう一度クリックします。
  10. [スロットリング] プルダウン リストを [オンライン] に戻します。

接続が復元されると、次のような通知が表示されます。

オフライン フローの全体像を示すスクリーンショット。

まとめ

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