PWA導入までの3ステップと、簡単なオフライン対応まで

# はじめに
以前から気になっていたPWAについて、導入するまでに最低限必要な3つのステップについて書きたいと思います。
この記事のゴールは、PWAでオフラインでもとりあえず動作するページを作成するまでです。

# PWAとは
**PWA（Progressive Web Apps）**は、Googleが推進するWeb上で**ネイティブアプリ同等のUXを実現できる技術**です。
PWAの機能には**プッシュ通知**、**ホーム画面に追加**、**コンテンツキャッシュ**などの機能があり、これらの機能を使用することでオフラインで動作するWebアプリを作成できたり、ネイティブアプリのようにユーザの端末にアプリをインストールすることができます。
アプリをインストールする際、ネイティブアプリのようにアプリストアを経由する必要はありません。

# 作成したデモ
https://yshrkn.github.io/pwa-test/

●リポジトリ
https://github.com/yshrkn/pwa-test

# 対応ブラウザ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/284587/dbe3ded6-29ad-2cf5-238b-0cdb89cd11b5.png)

最新ブラウザであれば概ね動くようです。
Safari、iOS（Edgeは未確認）は一部機能に対応していないようです。
iOS12で試してみましたが、「ホーム画面に追加」は動きませんでした。

iOSが対応している詳細な機能については、下記記事にまとまっています。

- [iOSの11.3からのPWA対応でできるようになったこと](https://medium.com/@takeshiamano/ios%E3%81%AE11-3%E3%81%8B%E3%82%89%E3%81%AEpwa%E5%AF%BE%E5%BF%9C%E3%81%A7%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8-313f638a172b)

# PWAを導入するための3ステップ
PWAを導入するには下記3つを対応する必要があります。

1. HTTPS対応
2. マニフェストの作成・読み込み
3. Service Workerの作成・読み込み

## 1. HTTPS対応
PWAに対応するためには**Service Worker**が必要ですが、**Service Worker**はHTTPSのサイト上でのみ動作します。
まずはHTTPS環境を用意する必要があります。
今回はお手軽にテストするために、HTTPSに対応しているGithub Pagesを使用しました。

**Service Worker**についてざっくり理解するには、下記ページが分かりやすいです。

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

## 2.マニフェストの作成・読み込み
PWAの基本的な設定を記述するマニフェストと呼ばれる`JSON`ファイルを用意する必要があります。
ファイル名は任意で問題ありません。一般的には`manifest.json`が使用されます。

### マニフェストファイルの主な設定項目
|プロパティ|説明|
|:-----------------|:------------------|
|name|サイトがユーザに表示される時に表示されるテキスト。|
|short_name|ユーザーホーム画面で表示されるテキスト。|
|start_url|ホーム画面に追加された際、アプリを起動して最初に呼び出されるページへのパス。|
|theme_color|アプリケーションの既定のテーマ色。タスク切り替えの時に使用される場合がある。|
|background_color|ホーム画面から起動した際にスプラッシュ画面の背景色として使用される。|
|icons|アプリのアイコンに使用する画像ファイルを配列で設定。|
|display|アプリの起動方法を設定する。`standalone `を指定するとネイティブアプリのようにURLバーなどが取り除かれ独立して起動。|

より詳しい設定項目については下記記事も参照してください。

- [ウェブアプリ マニフェスト](https://developers.google.com/web/fundamentals/web-app-manifest/?hl=ja)
- [ウェブアプリマニフェスト(MDN)](https://developer.mozilla.org/ja/docs/Web/Manifest)

### マニフェストの作成
では実際にルートに`manifest.json`を追加します。

```manifest.json
{
  "name": "PWA Test",
  "short_name": "PWA",
  "theme_color": "#2196f3",
  "background_color": "#2196f3",
  "display": "standalone",
  "start_url": "./index.html",
  "icons": [
    {
      "src": "./images/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ]
}

```

### マニフェストの読み込み
PWA対象の全てのHTMLの`head`タグで、作成した`manifest.json`を読み込みます。

```index.html
<link rel="manifest" href="./manifest.json">
```


## 3. Service Workerの作成・読み込み


### **Service Worker**の作成
**Service Worker**を作成します。
**Service Worker**のファイル名やファイルの置き場所は任意で構いません。
ファイルの置き場所によって**Service Worker**がキャッチできるイベントのスコープが決まります。
例えば、`/news`配下のみをPWA対応する場合、`/news`直下に**Service Worker**のファイルを配置することで`/news`フォルダ配下のみをPWAに対応できます。

```sw.js
// Cache name
const CACHE_NAME = 'pwa-sample-caches-v1';
// Cache targets
const urlsToCache = [
  './',
  './index.html',
  './pages/a.html',
  './pages/b.html',
  './pages/c.html',
  './css/style.css',
  './images/a.jpg',
  './images/b.jpg',
  './images/c.jpg',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches
      .open(CACHE_NAME)
      .then((cache) => {
        return cache.addAll(urlsToCache);
      })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches
      .match(event.request)
      .then((response) => {
        return response ? response : fetch(event.request);
      })
  );
});
```

ざっくりとプログラムの内容を記述します。
細かな部分の詳細な解説は下記ページをご参照ください。

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

まずはじめにオフラインで動作させるために、キャッシュ名を`CACHE_NAME`に、キャッシュ対象ファイルを`urlsToCache`に指定しています。

**Service Worker**は正常に登録されると`install`イベントが走るので、`install`イベント内で`caches`をオープンさせ、すべてのキャッシュ対象ファイルがキャッシュされるのを待機します。
キャッシュが成功したらキャッシュを追加、1ファイルでもキャッシュに失敗したらキャッシュは失敗します。

`fetch`イベントは**Service Worker**がインストール済みの状態でページ移動すると発生します。
`fetch`イベントが発生したとき、リクエストされたファイルがキャッシュの中にあれば該当するキャッシュを返し、なければリクエストされたファイルを`fetch`に渡してネットワークから取得した結果を返しています。

✳︎キャッシュを更新するコードは入っていないので、別途書いてあげる必要があります。

### **Service Worker**の読み込み

作成した`sw.js`をHTML側で読み込みます。
**Service Worker**対応ブラウザであれば`sw.js`を登録しています。

```index.html
<script>
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(registration => {
      console.log('ServiceWorker registration successful.');
    }).catch(err => {
      console.log('ServiceWorker registration failed.');
    });
  }
</script>
```

# 動作確認
「ホーム画面に追加」と、機内モードにしたオフライン状態でもしっかり閲覧できました！

![test4.gif](https://qiita-image-store.s3.amazonaws.com/0/284587/b839f6a0-67c9-1976-f9c5-d567121b6a9a.gif)

## 開発者ツールでファイルを確認する
Chromeの開発者ツールの「Application」タブの中に「Manifest」や「Service Worker」の状態、キャッシュの有無が確認できます。

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/284587/768e9f3e-6ab7-f499-2ccd-130a187600a7.png)


# 最後に
思ったよりもPWAの導入が容易にできて驚きました。
ちゃんとしたオフライン対応をするには、キャッシュの更新処理を書く必要があったりするので、これからもっと深く学んでみようと思います。
今回は触れませんでしたが、PWAにはプッシュ通知などの魅力的な機能がたくさんあるので、それらを使用してなにかアプリを作ってみたいと思います。