PWAを超簡単にオフライン表示に対応しよう【PWAの基準変更】

こんにちは。突然ですがPWAのオフライン表示対応が必須になります。
→**対応必須は流石に早すぎるということで撤回し一時見送られたそうです。**

ChromeのアップデートによりWebアプリをPWAとしてインストールできるかどうかの基準が見直されます。

# Googleからの発表
- **Chrome 89 以降**、PWAがオフライン時に有効な応答を返さない場合、**開発者ツールのコンソールに警告が表示される。**
- **Chrome 93(今年後半以降リリース予定)**以降、オフラインで適切に動作しないWebアプリはPWAとしてインストールできなくなる。

出典：[「Google Chrome 89」が公開 ～ゼロデイ脆弱性1件を含む47件の問題を修正](https://forest.watch.impress.co.jp/docs/news/1309734.html)

つまりオフラインでも使えるようにしてね。ってことですね。
現状、オフラインに対応しているPWAサイトはあまりない感じなので早急に対応をすべきでしょうね。
因みにオフライン表示に対応していないと以下の表示が出ます。（もちろん初めてじゃないです）
![Screenshot_20210307_144710 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513165/0cc14174-7835-a732-2f61-dc4bcea547e2.jpeg)

この問題を回避するには至って簡単で、**オフライン用のページを用意するだけでいいんです。**まぁ当たり前ですが。
なので今回はHTML+JSでオフライン対応させる雛形を作ります。

参考：[PWAでPUSH通知機能を実装。](https://qiita.com/tomomi0115/items/87bed293a415e8dde6c5)

# 実装
## 注意
PWAは**SSL対応が必須**です。ご注意ください。（一応localhostでは非SSLでも動きます）
### 構成

```
html
├ img
│ └ logo.png
├ index.html
└ sw.js
```

## manifest.json
PWAとして認識させるためにいつものmanifest.jsonを作成します。以下テンプレートです。

```manifest.json

{
    "name": "PWA Offline Test",
    "short_name": "PWA",
    "icons": [
        {
            "src": "/img/icon.png",
            "sizes": "144x144",
            "type": "image/png"
        }
    ],
    "start_url": "/",
    "display": "standalone",
    "theme_color": "#99d9ea"
}
```

Manifest.jsonの詳しい書き方は山ほどあるんで参考にしてください。

## ServiceWorker
PWAを動作させるために必要なServiceWorkerを書きます。

```sw.js
// バージョン定義
var CACHE_VERSION = 'ca-v1';
var DISP_VERSION = 'ca-d-v1';

// キャッシュの対象にするディレクトリ（css/jsは個別で追加）
var resources = [
  '/',
  '/img'
];

// キャッシュ追加
self.addEventListener('install', function (event) {
  console.log('ServiceWorker Install');
  event.waitUntil(
    caches.open(CACHE_VERSION)
      .then(function (cache) {
        console.log('cache.addAll');
        cache.addAll(resources);
      })
  );
});
// キャッシュ表示
self.addEventListener('fetch', function (event) {
  console.log('ServiceWorker fetch');
  event.respondWith(
    // キャッシュが存在するかチェック
    caches.match(event.request)
      .then(function (response) {
        if (response) {
          return response;
        } else {
          // キャッシュがない場合キャッシュに入れる
          return fetch(event.request)
            .then(function (res) {
              return caches.open(DISP_VERSION)
                .then(function (cache) {
                  console.log('cache.put');
                  cache.put(event.request.url, res.clone());
                  return res;
                });
            })
            .catch(function () {
              // 何もしない
            });
        }
      })
  );
});
// 古いキャッシュを削除
self.addEventListener('activate', function (event) {
  console.log('activate ServiceWorker');
  event.waitUntil(
    caches.keys()
      .then(function (keyList) {
        return Promise.all(keyList.map(function (key) {
          if (key !== CACHE_VERSION && key !== DISP_VERSION) {
            console.log('cache.delete');
            return caches.delete(key);
          }
        }));
      })
  );
  return self.clients.claim();
});
```

[この記事](https://idealive.jp/blog/2019/04/15/pwa対応%E3%80%80webサイトのオフラインで表示/)を参考にさせていただきました。

## htmlページ
オフラインで表示させるためのHTMLを書きます。その際、さっき書いたやつを読み込ませます。
以下テンプレート

```index.html
<head>
    <link rel="manifest" href="/manifest.json">
    <!-- 文字コード設定&モバイル対応化 -->
    <meta charset="utf8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <script>
        // ServiceWorkerを読み込ませる
        window.addEventListener('load', function () {
            if ('serviceWorker' in navigator) {
                navigator.serviceWorker.register('/sw.js')
                    .then(function (registration) {
                        return registration.pushManager.getSubscription().then(function (subscription) {
                            console.log("subscription", subscription)
                            if (subscription) {
                                return subscription
                            }
                            return registration.pushManager.subscribe({
                                userVisibleOnly: true
                            })
                        })
                    }).then(function (subscription) {
                        var endpoint = subscription.endpoint
                        console.log("pushManager endpoint:", endpoint)
                    }).catch(function (error) {
                        console.log("serviceWorker error:", error)
                    })
            }
        })
    </script>
</head>
<body>
    PWAのオフライン表示テスト（フロントページ）
    <br>
    <a href="https://hoge.com/index2.html">2ページ目へ</a>
    <!-- ページの遷移もできます。その際、遷移先ページにもServiceWorkerを読み込ませないとうまく機能しない場合があります -->
    <br>
    <img src="/icon.png">
</body>
</html>
```

これで実装は終わりです。ためしてみましょう。

# 動作確認
画面下部にインストールを促すポップアップが表示されるのでインストールしてみます。
![Screenshot_20210307_144842 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513165/470b5c97-d6b7-f035-3840-c075b469e7ee.jpeg)
![InkedScreenshot_20210307_144737 (1)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513165/22a02bd6-268b-bc4b-b436-b01ac520b9a5.jpeg)

インストールが終わると通知が表示されますので開いて表示を確認してください。
![InkedScreenshot_20210307_144751_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513165/55dee718-bf04-380c-ac6b-212b937c5b6c.jpeg)

ちゃんと表示ができたら**試しに機内モードにします**
![Screenshot_20210307_144819.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513165/d569771c-8d9c-8b54-faa9-9fd4f44aa58e.jpeg)

これで**アプリを開いても問題なく表示できたらOKです。**
![Screenshot_20210307_144918.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513165/e56ca09c-4e27-12ae-72e3-d3f8b5b11112.jpeg)
![Screenshot_20210307_144943 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513165/d9994ca3-faaf-d10c-07be-149972224fb9.jpeg)

はいちゃんと表示できてますね。ページの遷移も問題ないです。
以上PWAをオフライン化させてみました。基準変更まで短いので早急に対応してくださいねQiitaさん、**あなたのことです。**
【追記】Qiitaがオフライン表示に対応しました。有難うございます。
![Screenshot_20210522_124420.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513165/d9198de8-bbc9-c429-2e1d-1f681aa5a385.jpeg)


普段は[Twitter](https://twitter.com/tomox0115)や[ブログ](https://0115765.com)で色々書いてます。