ServiceWorkerと戦って負けた

# PWAをやりたい
最近以下のサイトを作った。
https://hatena-hotentry.netlify.com/

このサイトはスクレイピングした結果(データはfirebaseのrealtimeDatabaseに保存)を表示しているだけの、いわば読み物的なサイトなのでオフラインで動作できたらいいなあと思い、PWA化しようとした。そして負けた。


# やったこと

## 既存アプリに@vue/pwaの追加
このサイトはvue-cli 3で雛形を作って作成した。 雛形作成時の `vue create` を実行した時は特にpwaサポートを選択していなかった。

vue-cli 3のよかったところとして、雛形を元にすでに独自のアプリケーションを作成済みの状態だったとしても、

```console
$ vue add @vue/pwa
```
を実行すれば、pwaサポートを後からでもやってくれる。実行した結果、以下のファイルがプロジェクトに追加された。

![スクリーンショット 2019-03-31 10.35.27.png](https://qiita-image-store.s3.amazonaws.com/0/131625/b0d32aa6-7295-09e9-74b4-957a8d0bdf55.png)

# service workerを独自にカスタマイズ
プロジェクトのrootにvue.config.jsを作成し、pwaの設定を書いていく。
ここの各設定の詳細は以下の記事がわかりやすかった。
https://tech.mercari.com/entry/2017/12/19/workbox
https://blog.nagisa-inc.jp/archives/1132
公式のドキュメントは[こちら](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa)

```vue.config.js
const path = require('path');

module.exports = {
  pwa: {
    workboxPluginMode: 'GenerateSW',
    workboxOptions: {
      swDest: path.join(__dirname, '/dist/service-worker.js'),
      globDirectory: path.join(__dirname, '/dist/'),
      globPatterns: ['*.{html,js,css}'],
      runtimeCaching: [
        {
          urlPattern: /.*firebase.*/,
          handler: 'networkFirst',
          options: {
            cacheName: 'api',
          }
        },
        {
          urlPattern: /\.(png|svg|woff|ttf|eot)/,
          handler: 'cacheFirst',
          options: {
            cacheName: 'assets',
          }
        }        
      ]      
    },
  },
}

```

`workboxPluginMode` は `GenerateSW` か `injectManifest` を指定する。デフォルトは`GenerateSW`。
（`injectManifest` は細かい設定をしたい時用。事前にworkboxのコードを自分で書いておき、`swSrc` でパス指定する感じの使い方だが、よくわかっていない）

serviceworkerのキャッシュには `PreCache` と `RuntimeCache` の２種類がある。

`PreCache` はwebpackがバンドルしたjsファイル、cssファイルなどの静的リソースのキャッシュ。ServiceWorkerのインストールと同時にキャッシュを生成するため、初回ロード時でもすでにキャッシュが存在している。
`RuntimeCache` はAPIのレスポンスなど、動的リソースのキャッシュ。ネットワークリクエストが発生した際にキャッシュが作成されるため、2回目のロードのときにキャッシュが存在している。
この両方のキャッシュを用意しておくことでオフラインの環境になった時でもサイトが動く状態が作れる。

## PreCache

上記のvue.config.jsの

```js
      globDirectory: path.join(__dirname, '/dist/'),
      globPatterns: ['*.{html,js,css,json}'],
```

の部分がPreCacheの設定。vue-cliはデフォルトでdist配下にバンドルしたファイルを吐き出す。
`globDirectory` にそのパスを指定してあげて、 `globPatterns` の正規表現に一致したファイルが `PreCache` の対象になる。

## RuntimeCache
以下の設定が `RuntimeCache` の設定。

```js
      runtimeCaching: [
        {
          urlPattern: /.*firebase.*/,
          handler: 'networkFirst',
          options: {
            cacheName: 'api',
          }
        },
        {
          urlPattern: /\.(png|svg|woff|ttf|eot)/,
          handler: 'cacheFirst',
          options: {
            cacheName: 'assets',
          }
        }        
      ]      
```

api(firebaseのrealtimeDatabseからのデータ取得)からのレスポンスのキャッシュと、画像などのassets系のキャッシュの設定を書いている。
`handler` に書いている `networkFirst` はネットワークを優先し、ネットワークに繋がらなかったらキャッシュから返す。
`cacheFirst` キャッシュを優先しキャッシュになければネットワークから返す。という設定。
そのほか `handler` に指定できる種類は[ここ](https://developers.google.com/web/tools/workbox/modules/workbox-strategies)を参照


## ビルドの実行
yarn build(vue-cli-service build)を実行すれば、`swDest` に指定したところにservice-worker.jsが生成される。

上記のvue.config.jsで作成されたservice-worker.jsが以下。

```dist/service-worker.js
/**
 * Welcome to your Workbox-powered service worker!
 *
 * You'll need to register this file in your web app and you should
 * disable HTTP caching for this file too.
 * See https://goo.gl/nhQhGp
 *
 * The rest of the code is auto-generated. Please don't update this file
 * directly; instead, make changes to your Workbox build configuration
 * and re-run your build process.
 * See https://goo.gl/2aRDsh
 */

importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");

importScripts(
  "/precache-manifest.bd7ce76c63cdb56febe84b2ebb8ce649.js"
);

workbox.core.setCacheNameDetails({prefix: "hatena-hotentry"});

/**
 * The workboxSW.precacheAndRoute() method efficiently caches and responds to
 * requests for URLs in the manifest.
 * See https://goo.gl/S9QRab
 */
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});

workbox.routing.registerRoute(/.*firebase.*/, workbox.strategies.networkFirst({ "cacheName":"api", plugins: [] }), 'GET');
workbox.routing.registerRoute(/\.(png|svg|woff|ttf|eot)/, workbox.strategies.cacheFirst({ "cacheName":"assets", plugins: [] }), 'GET');
```

この作成されたservice-worker.jsは `vue add @vue/pwa` を実行した際に追加された `registerServiceWorker.js` で読み込まれる。

```registerServiceWorker.js
/* eslint-disable no-console */

import { register } from 'register-service-worker'

if (process.env.NODE_ENV === 'production') {
  register(`${process.env.BASE_URL}service-worker.js`, {
    ready () {
      console.log(
        'App is being served from cache by a service worker.\n' +
        'For more details, visit https://goo.gl/AFskqB'
      )
    },
    registered () {
      console.log('Service worker has been registered.')
    },
    cached () {
      console.log('Content has been cached for offline use.')
    },
    updatefound () {
      console.log('New content is downloading.')
    },
    updated () {
      console.log('New content is available; please refresh.')
    },
    offline () {
      console.log('No internet connection found. App is running in offline mode.')
    },
    error (error) {
      console.error('Error during service worker registration:', error)
    }
  })
}

```

ビルドが完了したら `Web Server for Chrome` を利用して動作確認を行う。
`Web Server for Chrome` の使い方のイメージはこちらが参考になった。
https://qiita.com/n11sh1/items/5d64c337ef927ac8d5d6

# 動作確認
## 初回のロード
service workerはちゃんと動いている。
![スクリーンショット 2019-03-31 17.55.39.png](https://qiita-image-store.s3.amazonaws.com/0/131625/df7b3d38-7945-d425-465f-ef8bc1d520ec.png)

preCacheもちゃんとされている
![スクリーンショット 2019-03-31 17.56.51.png](https://qiita-image-store.s3.amazonaws.com/0/131625/61864619-5111-7f4d-e9b0-bdae548a15ef.png)

## 2回目のロード
`RuntimeCache` がassetsの方しか存在しない。
![スクリーンショット 2019-03-31 17.58.02.png](https://qiita-image-store.s3.amazonaws.com/0/131625/aa2deee6-aba9-e08b-2480-31ae1f3d486b.png)

vue.config.jsで以下のように設定したapiのRuntimeCacheが存在しない。

```js
        {
          urlPattern: /.*firebase.*/,
          handler: 'networkFirst',
          options: {
            cacheName: 'api',
          }
        },
```

この状態だと、`PreCache` と assetsの `RuntimeCache` はserviceWorkerが返してくれるようになってるが、firebaseの方は通常通りのリクエストが行われている。
![スクリーンショット_2019-03-31_18_01_39.png](https://qiita-image-store.s3.amazonaws.com/0/131625/1a026b73-a6fb-8899-00fb-d901a1699bcf.png)

# realtimeDatabseはwebsocket
realtimeDatabseからデータ取得する際、 `wss://s-usc1c-nss-204.firebaseio.com/` にアクセスしており、正規表現的には

```
          urlPattern: /.*firebase.*/,
```

で一致する。

![スクリーンショット 2019-03-31 18.05.52.png](https://qiita-image-store.s3.amazonaws.com/0/131625/31581e31-b4e9-c05e-65d2-1027d5047cf3.png)

一致するのにキャッシュができてない原因は、もしかしたらwebsocketなのがダメなんじゃないかと思い、検索してみたらそれっぽいのが見つかった。


https://github.com/GoogleChrome/workbox/issues/624
> The Firebase Realtime API uses websockets to communicate with the server, and websockets can't be intercepted by service workers.

https://stackoverflow.com/questions/37741185/is-it-possible-to-intercept-and-cache-websocket-messages-in-a-service-worker-lik
> It's not possible for a service worker to intercept Web Socket traffic.

service workerはwebsocketの通信を傍受することはできないらしい。


他の手段でオフライン対応できないかと思って、firebaseSDKのマニュアルをみていたが、[ios](https://firebase.google.com/docs/database/ios/offline-capabilities?hl=ja)とか[android](https://firebase.google.com/docs/database/android/offline-capabilities?hl=ja)のfirebaseSDKだと、realtimeDatabaseのデータをローカルに保存しておくことでデータを永続させ、オフラインでも利用できるようにする便利メソッドが生えている。でもwebのSDKにはない。

ここら辺で負けた。


# 最後に
websocketを使ったwebをpwaでオフライン化することはできない？
pwaの知見が全然ないので方法があればコメントなどで教えていただきたいです。