PWA / Service Worker を使ってオフラインでもサイトを表示できるようにする

Shunsuke Sawada

サービスワーカーを使ってコンテンツをキャッシュすると、ユーザーがオフラインになったときでも動作するウェブサイトやウェブアプリを作ることができます。

基本的な使い方をメモ。

Service Worker を登録

index.html
1
2
3
4
5
6
7
<html>
<body>
  <h1>Hi, this is my App!</h1>
  <script src="app.js"></script>
</body>
</html>
app.js
1
2
3
4
5
6
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(function() {
      console.log('Service worker registered!');
    });
}
sw.js
1
// ここに Service Worker の処理を書いていきます

Service Worker をインストールして静的ファイルをキャッシュする

sw.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// キャッシュにバージョンを付けておくと、古いキャッシュを消す時に便利
var CACHE_STATIC_VERSION = 'static-v1';

// サービスワーカーのインストール
self.addEventListener('install', function(event) {
  console.log('[Service Worker] Installing Service Worker...');

  // キャッシュできるまで次の処理を待つ
  event.waitUntil(
    caches.open(CACHE_STATIC_VERSION)
      .then(function(cache) {
        console.log('[Service Worker] Precaching App...');
        // 何でもキャッシュできる。cssとかの中で更にリクエストが発生する場合は、動的にキャッシュする必要がある(後述)
        cache.addAll([
          '/',
          '/src/css/main.css',
          '/src/js/main.js',
          '/src/images/logo.jpg',
        ]);
      })
  );
});

キャッシュを利用してコンテンツを表示する

これでオフラインでもウェブサイトが表示できるようになります。
fetch イベントを監視して、キャッシュがあればhttpリクエストしないようにしています。

sw.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var CACHE_DYNAMIC_VERSION = 'dynamic-v1';

self.addEventListener('fetch', function(event) {
  console.log('[Service Worker] Fetching something ...');
  event.respondWith(
    // キャッシュの存在チェック
    caches.match(event.request)
      .then(function(response) {
        if (response) {
          return response;
        } else {
          // キャッシュがなければリクエストを投げて、レスポンスをキャッシュに入れる
          return fetch(event.request)
            .then(function(res) {
              return caches.open(CACHE_DYNAMIC_VERSION)
                .then(function(cache) {
                  // 最後に res を返せるように、ここでは clone() する必要がある
                  cache.put(event.request.url, res.clone());
                  return res;
                })
            })
            .catch(function() {
              // エラーが発生しても何もしない
            });
        }
      })
  );
});

新しいバージョンのキャッシュがあったら古いキャッシュを削除

サービスワーカーが activate されたタイミングで処理しないと 先に fetch とかが走って表示がおかしくなってしまうので注意。

sw.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
self.addEventListener('activate', function(event) {
  console.log('[Service Worker] Activating Service Worker...');
  event.waitUntil(
    caches.keys()
      .then(function(keyList) {
        return Promise.all(keyList.map(function(key) {
          if (key !== CACHE_STATIC_VERSION && key !== CACHE_DYNAMIC_VERSION) {
            console.log('[Service Worker] Removing old cache...');
            return caches.delete(key);
          }
        }));
      })
  );
  return self.clients.claim();
});

電波ないのにサイトが開けるって良いですね。コンテンツを先読みしておいて表示を爆速にすることもできるし。
ネイティブアプリ感覚で使えそう。
Chromeがメインで対応してますが、今後対応するブラウザも増えてくると思います。

参考

https://jakearchibald.com/2014/offline-cookbook/
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
https://developers.google.com/web/fundamentals/primers/service-workers/

Shunsuke Sawada