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

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

測定

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

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

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

  1. `Ctrl+Shift+J`(Mac の場合は `Command+Option+J`)を押して、デベロッパー ツールを開きます。
  2. [ネットワーク] タブをクリックします。
  3. Chrome DevTools を開き、[ネットワーク] パネルを選択します。
  4. [Throttling drop-down list] で、[Offline] を選択します。
  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 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 分(経過すると破棄されます)を選択しています。
  • このコードで最も重要な部分は 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 を実装し、オフラインのユーザー クエリが失われないようにして、接続が復旧したら再試行できるようにする方法を学びました。このデモはシンプルな検索アプリですが、チャットアプリやソーシャル ネットワークへのメッセージ投稿など、より複雑なシナリオやユースケースにも同様の実装を使用できます。