SPAのリビジョン管理workbox5 workbox-window 2020/7 追記

#2020/07追記

2020/7 追記
改良コード、そもそもspaはノード環境下ではないのと、定期的なアップデートでいくつかハマったので更新
開発環境、devビルド時は下記のリスナーできちんと発火する。が本番だと発火しないので、プロンプトを表示し、ユーザーが許可した場合に、新しいサービスワーカー登録するようmessageSWで通信。こちらもdev環境だとプロミスが帰ってくるが本番だと帰ってここなかったため、直後にリロードコードを仕込む。

```js

new GenerateSW({
      maximumFileSizeToCacheInBytes: 20000000,
      clientsClaim: true,
      skipWaiting: false,
      exclude: [/\.(?:png|jpg|jpeg|svg)$/],
      runtimeCaching: [
        {
          urlPattern: new RegExp('^(?!.*\/api\/).*$'),
          handler: 'NetworkFirst',
          options: {
            cacheName: `${process.env.APP_NAME}-${process.env.APP_ENV}`,
          }
        },
        {
          urlPattern: new RegExp('https:\/\/fonts.(gstatic|googleapis).com.*$'),
          handler: 'CacheFirst',
          options: {
            cacheName: 'google-fonts',
            expiration: {
              maxEntries: 30,
              maxAgeSeconds: 60 * 60 * 24 * 365
            },
            cacheableResponse: {statuses: [0, 200]},
          }
        },
        {
          urlPattern: /\.(?:png|jpg|jpeg|svg)$/,
          handler: 'CacheFirst',
          options: {
            cacheName: 'images',
            expiration: {
              maxEntries: 60,
              maxAgeSeconds: 30 * 24 * 60 * 60,
            },
            cacheableResponse: {statuses: [0, 200]},
          }
        }
      ]
    })

```

```js:app.js
import {Workbox} from 'workbox-window'
import store from '~/store/index'

if ('serviceWorker' in navigator) {
  const wb = new Workbox('/service-worker.js');
  const showSkipWaitingPrompt = (event) => {

    wb.addEventListener('controlling', () => {
      //skip waiting していない場合には、waiting→controllingにステータスが変更するため、リスナを設置してユーザーが更新を許可した場合にリロードが走るようにする。
      window.location.reload();
    })
     //なぜかcontrolling発火しない時の保険（原因は分からず）
    wb.addEventListener('activated', () => {
      window.location.reload();
    })
    store.dispatch('update/setStatus', true)
    store.dispatch('update/acceptUpdate', null)
  };

  wb.addEventListener('waiting', showSkipWaitingPrompt);
  wb.addEventListener('externalwaiting', showSkipWaitingPrompt);

  Vue.prototype.$registration = wb.register();
  manualSWUpdate();
  function manualSWUpdate() {
    setInterval(() => {
      wb.update();
    }, 300000)
  }
}

export const app = new Vue({
  router,
  store,
  vuetify,
  render: h => h(App)
}).$mount('#app')
```

swと通信する苦渋のコード

```js

  import {messageSW} from "workbox-window";

  export default {
    name: "UpdatePromptAlert",
    data: () => ({
      loading: false,
    }),
    computed: {
      ...mapGetters({
        update: 'update/status',
        acceptUpdate: 'update/accept',
      })
    },
    watch: {
      acceptUpdate(accept) {
        if (accept) {
          //sw = $registration
          this.$registration.then(res => {
            if (res.waiting) {
              messageSW(res.waiting, {type: 'SKIP_WAITING'})
              //fixme location reload, above messageSW somehow return promise in dev not prod
              //本当は、ステータスが遷移したことを確認するためきちんとリゾルブされてから、リロードかけたい
              location.reload()
            }
          })
        }
      }
    },
    methods: {
      accept() {
        this.loading = true
        this.$store.dispatch('update/acceptUpdate', true)
      },
    }
  }

```




#構成
Laravel6
vuex 3.5.1
vue 2.6.11
workbox-webpack-plugin 5.1.3
workbox-window 5.1.3
#お品書き 
- workbox-webpack-pluginの導入（GenerateSW）
- app.jsの編集（サービスワーカーの登録とライフサイクル別にしたいことの記述）
- vuexで新バージョンがあるかチェック
- コンポーネントにアラート追加

#注意事項
nuxt.jsをご利用の方へ 
おそらくindex.htmlのキャッシュをしない戦略だけで対応可能（nuxtで作られるjsは毎回名前が違うため、topHTMLだけよめばそこから最新のjsを引っ張ってこれる、はず）
[Nuxt.js(SPA)とFirebaseで強制リビジョン（バージョン）アップするならPWAモジュールを使おう](https://qiita.com/hecateball/items/29f726430d204c964f04)
上記はlaravelMIXを使っていると常に読み込むjsはapp.jsになるためうまくいかず。

その他
サービースワーカーを初めて触ったため、試行錯誤した結果まあ一定動くなというレベル感のコードです。その仕様はまずいなどありましたらご指摘いただけると幸いです。
キャッシュファイルのバージョンを手動で管理するとかのものは別記事にあったのですが、自分が求めた仕様とは異なり、あまり記事も見当たらなかったのでログとして残します。
[今回の構成の参考](https://samvloeberghs.be/posts/lessons-learned-on-offline-capabilities-with-service-workers-using-workbox-the-sequel)

#workbox-webpack-pluginの導入
laravelMixや素のwebpackを利用している場合に使うプラグインです。[参照](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin)

```terminal
npm i workbox-webpack-plugin --save-dev
```

#webpack.mix.jsの編集
GenerateSWを利用します。InjectManifestの方はより細かい設定をしたい時やプッシュ通知なども利用したいときに使います。[参照](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin)

```javascript:webpack.mix.js
import { GenerateSW } from 'workbox-webpack-plugin'

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/styles/app.sass', 'public/css')


mix.webpackConfig({
  output: {
    publicPath: "",
  },
  // ~中略~
  plugins: [
    new GenerateSW({
      maximumFileSizeToCacheInBytes: 10000000,//jsファイルが５mb以上になっているとデフォルトの設定ではprecacheできないので要件に応じて設定してください。
      clientsClaim: true,
      skipWaiting: true,
      runtimeCaching: [
      //リクエストするURLに応じて、キャッシュ戦略を指定することができます。[参照](https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-build.html#.RuntimeCachingEntry)
        {
          urlPattern: new RegExp('https:\/\/fonts.(gstatic|googleapis).com.*$'),
          handler: 'CacheFirst',
          options: {
            cacheName: 'google-fonts',
            expiration: {
              maxEntries: 100,
              maxAgeSeconds: 60*60*24
            },
            cacheableResponse: { statuses: [0, 200] },
          }
        }
      ]
    })
  ],
  //~中略~
})
```
*workbox5からはトランスパイル先のディレクトリや対象ファイルの拡張子での指定などをわざわざしなくても、webpackの基本設定をもとにうまいことやってくれます。がpublicPathを明示的に何も付けないように指定しないと、生成されるservice-wroker.jsのプリキャッシュ指定ファイルのパスが`//js/app.js`のようになってしまうため、先頭の二重スラッシュを避けるようpublicPathを明記します。

#app.jsの編集
トランスパイルされた、service-worker.jsの登録とライフサイクル時の処理を記載します。
workbox-windowの導入（ライフサイクルのそれぞれの時点でフックして処理をかけるようにしてくれる優れもの）[参照](https://developers.google.com/web/tools/workbox/modules/workbox-window)
> If you do call skipWaiting() then it's best to inform users of the update once the new service worker has activated,
[この部分](https://developers.google.com/web/tools/workbox/modules/workbox-window#when_an_updated_version_of_the_service_worker_is_found)

webpack.mix.jsで書いたようにskipWaiting()ですぐに新しいサービスワーカーを利用する設定にしたため上記で言われているようにactivatedのタイミングでプリキャッシュしたjsの新しいバージョンがあることをユーザーに伝える。

```terminal
npm i workbox-window --save-dev
```