PWA サンプルを動かしてみた

## やること
PWAのサンプルをいじって理解する。

## 入れる機能
更新がないコンテンツ（AppShell）はSWインストール時にデータをCacheに格納し、、それ以降はCacheのみをみる

更新が頻繁にあるコンテンツは、まずCacheのデータを使い、同時にリクエストを送信して帰ってきたレスポンスを使って再表示する。

ネットワークに繋がらなくてもCacheがないページは見栄えの良いエラーページを見せるようにする

## 完成形のデザイン

<img width="491" alt="スクリーンショット 2018-12-02 16.07.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/252596/150f31f4-3507-376c-c48b-242a8be36a64.png">

左上のメニューを開いてHelpという文字をクリックすると別のページに飛ぶ

<img width="409" alt="スクリーンショット 2018-12-02 16.08.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/252596/7ddb524f-03e8-e9cd-a7f5-33b315c1e669.png">


## 実装
ずっと更新されないコンテンツと頻繁に更新されるコンテンツごとにキャッシュ戦略を練りたい。

入れる機能の部分でも紹介したが、上記写真の上にある写真はヘッダーなので、こいつは変える予定がない。

また、ヘッダーのデザインやメニューのデザインなど、いわゆるAppShellと呼ばれる外側の見栄えに関してはそうそう変えることない。

よって、SWをinstallした時点で全てCacheする。ただこのままでは、SWを更新した時やブラウザを開き直した時など、　SWをインストールするというタイミングで無駄に通信が発生してしまう。

よってfetchイベントの時に、更新されないコンテンツへのURLかを確認し、当てはまればfetchせずにcacheを返すことにする。またここで注意したいのは、このままだと一番最初にユーザーがページに訪れてSWをインストールしたとしても上の処理によっていつまでたってもCacheされなくなってしまう。

なので、更新されない静的コンテンツに関しては、Cacheにあればそれを返し、Cacheになければfetchしてくる。これによって初回のインストール時以外は、Cacheから読むことになる。

これがコードだ。

```
self.addEventListener("install", function (event) {
  console.log("[Service Worker] Installing Service Worker ...", event);
  event.waitUntil(caches.open(CACHE_STATIC_NAME).then(function (cache) {
    console.log("Service Worker  Precaching App shell")
    cache.addAll([
      "/",
      "/offline.html",
      "/index.html",
      "/src/js/app.js",
      "/src/js/feed.js",
      "/src/js/material.min.js",
      "/src/css/app.css",
      "/src/css/feed.css",
      "/src/images/main-image.jpg",
      "https://fonts.googleapis.com/css?family=Roboto:400,700",
      "https://cdnjs.cloudflare.com/ajax/libs/material-design-lite/1.3.0/material.indigo-pink.min.css",
      "https://fonts.googleapis.com/icon?family=Material+Icons"
    ])
  }));
});
```

ネットワークエラーの時に読み込む最後の手段としてのoffline.htmlもここで取りに行くのを忘れないようにされたい。

そしてfetchイベントの中身はこちら

```
function isInArray(url) {
  for (var item of staticFiles) {
    console.log(url.match(/*item*/))
    if (url.match(/*item*/)) {
      return true
    }
  }
  return false
}



function requestAfterCache (cacheType, event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      // "https://httpbin.org/get"
      // 上のURL出なかった場合は、Cacheを見にいきなければfetchしてcacheするという戦略
      if (response) {
        console.log("there a cahce!!")
        return response
      } else {
        console.log("there are no cache")
        return fetch(event.request)
          .then(function (res) {
            return caches.open(cacheType).then(function (cache) {
              cache.put(event.request.url, res.clone())
              return res
            })
          })
          .catch(function (err) {
            return caches.open(CACHE_STATIC_NAME)
              .then(function (cache) {
                // 通信に失敗したのがhtmlファイルの要求だった場合のみoffline.htmlを表示
                if (event.request.headers.get("accept").includes("text/html")) {
                  return cache.match("/offline.html")
                }
              })
          })
      }
    })
  )
}


self.addEventListener("fetch", function (event) {
 if (isInArray(event.request.url)) {
    requestAfterCache(CACHE_STATIC_NAME, event)
  } else {
    requestAfterCache(CACHE_DYNAMIC_NAME, event)
  }
})
```

これで最初だけ通信が発生し、２回目以降はCacheをみに行ってくれているのがconsoleから分かります

最後は、動的な（頻繁に更新があるコンテンツ）です。パフォーマンスとアップデート性を両方兼ね備えたCacheThenNetworkというキャッシュ戦略をとります。

まずはCacheをみて、あればCacheのデータを表示して、同時並行で新しいコンテンツがないかをリクエストして、帰ってきたレスポンスを使用して更新するというものです。

上記の写真でいうと、下の写真が頻繁に更新されるやつです。

実際に写真を表示させて、その後コンテンツをアップデートするときちんと更新されているか確認してみましょう。

次の写真は、https://httpbin.org/getにjsで通信を飛ばして返り値を使ってUIを作っています。

<img width="491" alt="スクリーンショット 2018-12-02 16.07.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/252596/5b49bfda-ff9c-5d16-d070-4842ff159df5.png">


本来なら返ってくるjsonの値を変更したいところですが、jsonをCacheする方法は今回は省きたいので、便宜的にjsonが返ってきた後に挟むimgタグの中身を変えてみたいと思います。

まずhttps://httpbin.org/getへのリクエストのfetchコードは以下です。
動的に作るUIの作成に必要な画像もこのCache戦略を使用します。

```
self.addEventListener("fetch", function (event) {
  var url = "https://httpbin.org/get"
  var url2 = "/src/images/sf-boat.jpg"
  if (event.request.url.indexOf(url) > -1  ||  event.request.url.match(/*url2*/) ) {
    console.log("aaaaawgegwegagawg")
    event.respondWith(
      caches.open(CACHE_DYNAMIC_NAME).then(function (cache) {
        return fetch(event.request).then(function (res) {
          cache.put(event.request, res.clone())
          return res
        })
      })
    )
```

次にSW.jsではなく実際にUIを形成しているJSファイルのコードは以下です

```
var url = "https://httpbin.org/get"
fetch(url)
  .then(function(res) {
    return res.json();
  })
  .then(function(data) {
    console.log("From web", data)
    clearCards()
    createCard();
  });


var networkDataReceived = false

if ("caches" in window) {
  caches.match(url).then(function(response) {
    if (response) {
      return response.json()
    }
  }).then(function(data) {
    console.log("from cache", data)
    if (!networkDataReceived) {
      createCard()
    }
  })
}
```

createCard関数で画像を使ってカードのデザインを作り上げてます。
万が一ネットワークを介してデータを取ってきた後にCacheの読み込みが終わって上書されると困るので、リクエストが完了したかどうかの変数で処理を分けてます。

これによって２回目ページを読み込んだ際、サーバのデータが変わっていれば、それが反映されるようになりました。

ちなみにネットワークに繋がっていなければCacheをみに行き、それでもなければMimeTypeに応じて表示するページを変えるのが良いそうです。

本来ならBackgroundSyncやJsonデータのCacheなどまだまあSWのメリットはたくさんありますが。これだけでもPWAのすごさがお分り頂けたのではないでしょうか？