workbox + webpackでServiceWorkerを使いPWA化してみた

## PWAとは
[Googleの記事](https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/)によると、

>プログレッシブ ウェブアプリはウェブとアプリの両方の利点を兼ね備えたアプリです。ブラウザのタブで表示してすぐに利用することができ、インストールの必要はありません。使い続けてユーザーとの関係性が構築されていくにつれ、より強力なアプリとなります。不安定なネットワークでも迅速に起動し、関連性の高いプッシュ通知を送信することができます。また、ホーム画面にアイコンを表示することができ、トップレベルの全画面表示で読み込むことができます。

つまり、ネイティブアプリのような機能(プッシュ通知やホーム画面にアイコンを表示など)を提供しながらも、通常のWebページ同様ブラウザでアクセスできるアプリケーション


## 特徴
[Googleが提示するPWAの特徴](https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/?hl=ja#_1)

| 特徴             | 必要なもの           |
|:-----------------|:------------------|
| オフラインでの動作   | ServiceWorker      |
| プッシュ通知        | ServiceWorker      |
| ホーム画面追加      | [Web App Manifest](https://developer.mozilla.org/ja/docs/Web/Manifest) |



## 対応状況(2018/09/20)
[Can I Use](https://caniuse.com)


### ServiceWorker

<img width="1263" alt="スクリーンショット 2018-09-20 11.29.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/142086/24a05fc9-43b4-56f6-c935-e15f2337ae50.png">


### Web App Manifest
<img width="1263" alt="スクリーンショット 2018-09-20 11.29.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/142086/ec54e5e3-0788-ec80-e02d-ff29ebb4d6ff.png">


## 実現したこと
- ServiceWorkerを有効に
- manifest.jsonを設置
- HTTPS対応


### ServiceWorkerを有効に

#### workbox-sw + webpackで実現

[workbox](https://developers.google.com/web/tools/workbox/)

![workbox.png](https://qiita-image-store.s3.amazonaws.com/0/142086/76046bbd-73cd-54a4-6600-fb72d6ce38f1.png)

- Google製ServiceWorkerのコードを生成を支援してくれるライブラリ


#####インストール
```
npm install --save-dev workbox-sw

# webpackでworkboxを使うためにプラグインもインストール
npm install --save-dev workbox-webpack-plugin
```


##### webpack
```js:webpack.config.js
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const dist = __dirname + '/dist'

// webpack config
{
  plugins: [
    // Other webpack plugins...
    new WorkboxWebpackPlugin.GenerateSW({
      globDirectory: dist,
      globPatterns: ['*.{html,js,css}', 'images/**/*.{jpg,jpeg,png,gif,webp,svg}'],
      swDest: dist + '/sw.js',
      clientsClaim: true,
      skipWaiting: true,
    }),
  ]
}
```


###### インストールした`workbox-webpack-plugin`をimportし、以下のオプションを設定

- `globDirectory:` precache対象とするディレクトリ
- `globPatterns:` precache対象とするファイルのパターン
- `swDest:` workboxが生成するServiceWorkerのファイルのパス
- `clientsClaim:` 最新のサービスワーカーに、アクティブになるとすぐにすべてのクライアントを制御するよう指示
- `skipWaiting:` 待機フェーズに入ると最新のServiceWorkerにアクティベーションを指示

ここまではprecacheの設定で、静的なコンテンツを事前にキャッシュさせることができる

###### runtimeCachingの設定を追加

動的コンテンツはruntimeCachingを行う

GoogleAPIのURLにマッチするurlPatternのときに`api`というnameで3日間100件までのデータをキャッシュする例(ステータスコード200の場合のみ)

```js:webpack.config.js
    new WorkboxWebpackPlugin.GenerateSW({
      globDirectory: dist,
      globPatterns: ['*.{html,js,css}', 'images/**/*.{jpg,jpeg,png,gif,webp,svg}'],
      swDest: dist + '/sw.js',
      clientsClaim: true,
      skipWaiting: true,
      runtimeCaching: [
        {
          urlPattern: new RegExp('/'),
          handler: 'staleWhileRevalidate',
        },
        {
          urlPattern: new RegExp('https://www.googleapis.com/'),
          handler: 'cacheFirst',
          options: {
            cacheName: 'api',
            expiration: {
              maxEntries: 100,
              maxAgeSeconds: 72 * 60 * 60
            },
            cacheableResponse: { statuses: [0, 200] },
          }
        },
      ],
    }),
```


###### ServiceWorkerを登録

```js:src/index.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('./sw.js')
  })
}
```



### manifest.jsonを設置

- ホーム画面などにWebアプリやサイトを表示する方法を制御し、ユーザが起動できる対象や起動時の見た目（スプラッシュ画面など）を指定できる

#### manifest.jsonを作成
- [manifest.jsonジェネレータ](https://app-manifest.firebaseapp.com/)
  - 512×512の画像をUploadするだけで必要な大きさの画像ファイルをまとめてzipにしてくれるのが便利
- 192×192のpngアイコンが登録されていないと、Chrome for Androidで`ホーム画面に追加`バナーが表示されない



```json:manifest.json
{
  "name": "test app",
  "short_name": "test app",
  "theme_color": "#3f51b5",
  "background_color": "#ffea00",
  "display": "standalone",
  "Scope": "/",
  "start_url": "/?utm_source=homescreen",
  "icons": [
    {
      "src": "./assets/img/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "./assets/img/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "./assets/img/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "./assets/img/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "./assets/img/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "./assets/img/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "./assets/img/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "./assets/img/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "splash_pages": null
}
```



- `name`, `short_name`は必須
- `"display": "standalone"`でChromeからアドレスバーがなくなりネイティブっぽくなる
- `"start_url"`でホーム画面から飛んだときのパラメーターなど設定できる



#### htmlで読み込み

`<head></head>`の中で読み込む

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



### HTTPS対応



ServiceWorkerを有効にする条件としてHTTPSに対応していなければならない

#### Netlifyでdeployする
[Netlify](https://www.netlify.com/)
<img width="178" alt="スクリーンショット 2018-09-26 14.03.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/142086/86f837ad-e800-e428-3eb0-7d788f68090f.png">

- 無料でhttpsでのdeployに対応している
- Github, GitLab, Bitbucketなどからのdeployが可能

参考：[高機能ホスティングサービスNetlifyについて調べて使ってみた](https://qiita.com/TakahiRoyte/items/b7c4d1581df1a17a93fb)


## おわりに
- workboxを使うことでServiceWorker対応を、Netlifyを使うことでhttps化を手軽に行うことができた
- workboxはwebpushには未対応のためpush通知を利用したければ自分でロジックを書きworkboxで生成されたコードとマージしなければならない

参考
[PWA(Progressive Web Apps)と戯れてみる](https://qiita.com/j16a/items/d3f7f7c3bb283bb20f23)
[ServiceWorkerを簡単に書けるworkbox-swの使い方]
(https://qiita.com/nazonohito51/items/32b61cabdac8b24769bd)