一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

一休.comにService Worker(Workbox)を導入しました

f:id:ryo-utsunomiya:20191128104547p:plain

こんにちは。宿泊事業本部の宇都宮です。

この記事は、一休.com Advent Calendar 2019の2日目の記事です。

今日は、一休.com( https://www.ikyu.com )にService Worker + Workboxを導入した件について書きます。

Service Workerとは

Service Workerはブラウザのバックグラウンドで動作するJavaScriptで、PWA(Progressive Web Apps)の基盤技術です。

Service Worker の紹介 https://developers.google.com/web/fundamentals/primers/service-workers?hl=ja

はじめてのプログレッシブウェブアプリ https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/?hl=ja

Service Workerを導入することには2つの意義があると考えています。

(1) PWAの機能を提供するための前提となる (2) プログラマブルなブラウザキャッシュ機構の導入によるパフォーマンス改善ポイントの追加

一休.comでの導入内容

一休.comでも、Service Workerを導入しました。ただし、ミニマムに始めるため、サイトの既存の動作に極力影響しない形で導入しました。

  • PWAモードは無効化
    • したがって、Add to Home Screen(A2HS)なし
  • オフラインページ( https://www.ikyu.com/offline.html )の追加
  • Service Workerによるキャッシュはstyleのみで実験的に開始 => script, image, fontにも拡大
    • 今後、静的ページのキャッシュを追加予定

実装の詳細

service workerのエントリーポイントとなるスクリプトは、webpackでバンドルしたjsに含めています。ほとんどの画面ではこのスクリプトが呼ばれます。

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js').then(
      registration => {
        console.log(
          `ServiceWorker registration successful with scope: ${registration.scope}`,
        );
      },
      err => { /* エラーハンドリング */ },
    );
  });
}

このスクリプトは、ブラウザがService Workerを利用可能な場合には、sw.js をService Workerに登録します。実際にService Workerで実行されるスクリプトは https://www.ikyu.com/sw.js にあります。

sw.js では、Workboxという、Service Workerでキャッシュ管理を宣言的に行えるようにするライブラリを使っています。

オフラインページ

オフラインページ( https://www.ikyu.com/offline.html )は、↓のようなスクリプトでキャッシュできます。

const OFFLINE_PAGE = '/offline.html';
workbox.precaching.precacheAndRoute([
  OFFLINE_PAGE,
  '/dg/image/logo/neologo2.gif', // オフラインページで使ってるロゴ
]);

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

これによって、ネット接続が切れている場合でも静的ページを表示できます(↓は機内モードなのでネット接続なし)。

f:id:ryo-utsunomiya:20191128102816p:plain:w320

実行時キャッシュ

実行時にキャッシュさせるリソースは以下のように宣言します。

workbox.routing.registerRoute(({ url, request }) => {
  const hostnames = [
    // キャッシュを許可するドメイン名のリスト
    'www.ikyu.com',
    'www.img-ikyu.com',
  ];
  const types = [
    // キャッシュを許可するリソースの種別
    'font',
    'script',
    'style',
    'image',
  ];
  return (
    hostnames.some(hostname => url.hostname === hostname) &&
    types.some(type => request.destination === type)
  );
}, new workbox.strategies.StaleWhileRevalidate());

ここでは fetch standardの request.destination を使って、リソースの種別によってキャッシュの可否を決めています。 https://fetch.spec.whatwg.org/#concept-request-destination

このキャッシュはブラウザのデフォルトキャッシュに優先されます。また、Stale While Revalidate ストラテジーでキャッシュが管理されるため、リソースが更新されている場合は、次回リクエスト時には新しいリソースに差し替わります。

参考:Stale-While-Revalidate ヘッダによるブラウザキャッシュの非同期更新 https://blog.jxck.io/entries/2016-04-16/stale-while-revalidate.html

また、デフォルトは NetworkOnly になっていて、キャッシュ対象でないリソースの取得時には、Service Workerは何もしません。

// デフォルトはNetworkOnly(service workerは何もしない)
workbox.routing.setDefaultHandler(new workbox.strategies.NetworkOnly());

キャッシュの確認方法

Chrome DevToolsのApplicationタブで Cache > Cache Storage > workbox-xxx という項目を見ると、Service Workerがキャッシュしているファイルを確認できます。

f:id:ryo-utsunomiya:20191128103128p:plain:w375

Developer Toolsの注意点

Service Worker(Workbox)を入れると、ネットワークリクエストをService Workerが中継するようになるため、Developer ToolsのNetworkタブの見方が変わります。

通常のネットワークリクエストのログに加えて、Service Workerがネットワークリクエストを中継したことを示すfetchのログが出るようになります(Networkタブのログに、実際のリクエストのログとService Workerのログの両方が出るようになります)。

↓のようにログが2行出ていても、2回リクエストが飛んでいるわけではありません。

f:id:ryo-utsunomiya:20191128103444p:plain:w375

⚙️(歯車)のついているリクエストは、Service Workerが中継したことを示しているだけで、無視して良いです。

また、以下のように、cssや画像などのService WorkerログもXHR(XHR and Fetch)タブに登場します。実際のリクエストログは CSS や Img といった専用タブにあります。

f:id:ryo-utsunomiya:20191128103531p:plain:w375

これらの影響で、Networkタブがかなりノイジーになるので、Service Workerのログをフィルタリングしたいところですが、今のところChrome/Firefoxではフィルタリング機能は提供されていないようです。

Service Workerのデバッグ

DevToolsの Application > Service Workes にはService Worker関係のデバッグ機能が用意されています。たとえば、「Bypass for Network」を使うと、Service Workerをバイパスする(ブラウザにネットワークアクセスを強制する)ことができます。

f:id:ryo-utsunomiya:20191128154234p:plain:w375

Progressive Web App のデバッグ https://developers.google.com/web/tools/chrome-devtools/progressive-web-apps

Web App Manifest

Service Workerとは直接関係ないですが、PWA絡みでWeb App Manifestについても触れておきます。

Web App Manifestは、PWAが動作するための要件の一つで、PWAとしての動作モードなどを指定します。

一休.com のmanifestは https://www.ikyu.com/manifest.json にあります。

Web App Manifestで一番重要な設定は "display" で、これによって動作モードが変わります。 https://developers.google.com/web/fundamentals/web-app-manifest

一休.com では現在 "display": "browser" を使用しており、これはPWAとしては動作しないモードです。 このようにしているのは、 (1) 一休ユーザの5割(モバイルでは7割)を占める Safari では、PWAの体験が良くないこと (2) 2017年頃に一休レストランでA2HSを試したところ、ほとんど使われなかったこと が理由です。

SafariのPWAモードが改善したり、A2HSを促すための良いタイミングが見つかったりしたら、"display": "standalone" などPWAとして動作するモードに切り替えようと思っています。。

今後の展望

Service Worker、今後しばらくはキャッシュ強化などのパフォーマンス改善目的で使用し、将来的にPWA化を進めたくなった時に備える、という感じで、引き続きやっていきます。

謝辞

一休.comのService Worker導入に当たっては、Googleの id:sisidovski さんに多大なご協力をいただきました。この場を借りてお礼申し上げます。