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

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

測定

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

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

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

  1. Ctrl+Shift+J キー(Mac の場合は Command+Option+J キー)を押して DevTools を開きます。
  2. [Network] タブをクリックします。
  3. Chrome DevTools を開き、[Network] パネルを選択します。
  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. ソースを表示するには、[ソースを表示] を押します。
  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. [Throttling] プルダウン リストを再び [Offline] に戻します。
  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() に渡して、ユーザーに適切なテキストを表示します。

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

次に、ワークボックスのバックグラウンド同期を実装してオフライン クエリを保持し、接続が回復したことをブラウザが検出したときに再試行できるようにします。

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

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

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

おわりに

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