一番シンプルなPWAを作ってみよう

# 一番シンプルなPWAを作ってみよう

## PWAってなに？
PWAというのはProgressive Web Appの略で、ほげほげ・・・以下略。
コレを読んでる人はそこはもうええわ、って感じですよね笑。実践だけしていきます。

## 今回のゴール
とりあえずPWAの要件を満たす最低限のHelloWorldアプリケーションを作ります。
シンプルにPWAの条件と実装方法を理解することだけを目的とします。条件は以下の通りです。

- ライブラリ使わない
- フレームワークも使わない
- 全部自前で実装

ちなみにここで作るアプリは[Medium](https://medium.com/james-johnson/a-simple-progressive-web-app-tutorial-f9708e5f2605)を見ながら作ったものであり、自分のアイデアではありません。
Medium記事を書いてくださった作者様には感謝しています！

## 実践
### セットアップ
以下、フォルダ構成になります。任意の場所にこのフォルダ構成を作ります。

```shell
/HELLO-PWA    # Project root
    /css      # StyleSheets
    /images   # Image files
    /js       # JavaScripts
```

### まずは画面を作ってみる
PWAを意識したHTMLを書く必要があります。最低限、以下の２点に留意すれば良いようです。

1. JavaScriptが無効化されている場合でも画面が表示できるよう配慮する！これを守ることで回線状況が悪い場合や古いブラウザからアクセスされた場合も真っ白な画面を表示してしまうことだけは避ける可能性が高くなります。
2. レスポンシブな画面としていろんなデバイスからアクセスされる可能性がある点に留意しましょう。モバイルフレンドリーであれ、ということだそうです。

最低限の対応として、いつもの *viewport* metaタグを利用しましょう。Project rootフォルダにindex.htmlを作ります。

```html:index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Hello World</title>
  <link rel="stylesheet" href="css/style.css">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
<body class="fullscreen">
  <div class="container">
    <h1 class="title">Hello World!</h1>
  </div>
</body>
</html>
```

CSSも格納します。

```css:css/style.css
body {
  font-family: sans-serif;
}

/* コンテンツがブラウザのウインドウを100%使うようにしましょう */
html,
.fullscreen {
  display: flex;
  height: 100%;
  margin: 0;
  padding: 0;
  width: 100%;
}

/* コンテンツは画面の中央に来るようにね*/
.container {
  margin: auto;
  text-align: center;
}

.title {
  font-size: 3rem;
}
```

ここで一度、ブラウザで開いてみましょう。ちなみに私はVisual Studio Code + Go Liveの組み合わせです。
![Screen Shot 2019-10-14 at 18.54.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271670/fa680ce5-430f-4915-8b3c-84fd0f278b69.png)
表示されましたね。ただ、これではPWAとは言えません。自信を持ってPWAと言えるようになるためには何をすれば良いのでしょう？

## どのくらいPWA化出来ているかを評価しよう

Google謹製のChrome拡張「Lighthouse」を導入するとWebページのいろいろな評価ができます。これを使ってこのアプリがどの程度PWA化出来ているのか、計測していきましょう。
![Screen Shot 2019-10-14 at 18.55.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271670/cc3dd2bd-d03d-2de9-61eb-eebab5558d3c.png)

F12の開発者コンソールにAuditsタブが追加されているのがわかりますね。画面内に「Progressive Web App」チェックボックスがあるのが見えると思います。このチェックを入れることで、PWA化出来ているか、計測してくれます！スゴイですよね。画面下部に「Run audits」ボタンがあるので早速押して評価してみましょう。
![Screen Shot 2019-10-14 at 18.56.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271670/02e25764-0368-9fd5-1581-19201ead9bc5.png)
なんとなく、全くPWAじゃねーし！って言われてる感じですね笑。下の方にスクロールすると以下のように丁寧に評価ポイントを示してくれています。これを緑にしていけば、PWA度合いが上がっていくわけですね！
![Screen Shot 2019-10-14 at 18.58.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271670/454b8cb9-4940-9be6-9e7c-56ba349f511a.png)

## PWA化しよう！

### ServiceWorkerを追加しよう
PWAといえばServiceWorker笑。知らない人のために１行で説明すると、「バックグラウンドで、そのWebページを開いていないときですら、JavaScriptを自動的に実行してくれる機能」です。ServiceWorkerを実装することにより初回アクセス時にJSがダウンロードされ、次回以降は自動的にスクリプトが実行することができるようになります。早速、Project rootにsw.jsを格納します。

```javascript:sw.js
var cacheName = 'hello-pwa';
var filesToCache = [
  '/',
  '/index.html',
  '/css/style.css',
  '/js/main.js'
];

/* サービスワーカー起動して、コンテンツをキャッシュする */
self.addEventListener('install', function(e) {
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(filesToCache);
    })
  );
});

/* オフライン時はキャッシュからコンテンツを取得する */
self.addEventListener('fetch', function(e) {
  e.respondWith(
    caches.match(e.request).then(function(response) {
      return response || fetch(e.request);
    })
  );
});
```
cacheName、filesToCacheの２つの変数が登場しました。読んで字の如し、ですので説明は省略しますがなんとなくそういうことです。次にこのsw.jsを呼び出すためのにJavaScriptを新規作成し、HTMLを修正します。

```javascript:js/main.js
window.onload = () => {
    'use strict';
  
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker
               .register('./sw.js');
    }
  }
```

```html:index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Hello World</title>
  <link rel="stylesheet" href="css/style.css">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body class="fullscreen">
  <div class="container">
    <h1 class="title">Hello World!</h1>
  </div>
  <script src="js/main.js"></script>
</body>
</html>
```

これでオフライン対応が完了しました。試しにサーバを止めてからアプリにアクセスしても画面が表示されます。試してみてください。これがServiceWorkerによるCache機能です。（Incognitoモードだとcacheされないので気をつけてくださいね。念の為）

### Manifestを追加しよう
Manifestファイル(Json)に、このアプリの挙動や見た目を表現します。最低限の記載をしていきます。Project rootにmanifest.jsonを作成します。

```json:manifest.json
{
    "name": "Hello World",
    "short_name": "Hello",
    "lang": "en-US",
    "start_url": "/index.html",
    "display": "standalone",
    "background_color": "white",
    "theme_color": "white"
}
```

| 名前             | 説明                                                                             |
|------------------+---------------------------------------------------------------|
| name             | アプリの名前。インストール時のpromptに表示されます。                  |
| short_name       | アプリのアイコンに表示されます。                                    |
| lang             | デフォルトの利用言語です。                                         |
| start_url        | アプリ起動時のURLです。                                           |
| display          | アプリのガワの見た目を選択します。nativeっぽくする場合はstandaloneを指定します。|
| background_color | アプリ起動時のスプラッシュスクリーンの色を指定します。                   |
| theme_color      | ツールバー、タスクスイッチャーの色を指定します。                        |

manifestを読み込むようにHTMLも修正しましょう。どのテーマカラーを使うのかはHTML内に改めて記載します。

```html:index.html
<head>
...
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="white"/>
...
</head>
```

### アプリのアイコンを用意する
これで最後です。PWAはnativeアプリのようにアイコンをホーム画面に設置されるのが期待値ですので、アプリのアイコンを準備せねばなりません。面倒ですが、Windows, Mac/iPhone, Androidに合わせて最低でも以下の7種類のアイコンファイルを用意する必要があります。

- 128x128 px
- 144x133 px
- 152x152 px
- 192x192 px
- 256x256 px
- 512x512 px
- 16x16   px (favicon)

適当なイメージを作成したら、manifest.jsonを修正しましょう。short_nameタグの後ろに追加します。

```json:manifest.json
{
    "name": "Hello World",
    "short_name": "Hello",
    "icons": [{
      "src": "images/hello-icon-128.png",
        "sizes": "128x128",
        "type": "image/png"
      }, {
        "src": "images/hello-icon-144.png",
        "sizes": "144x144",
        "type": "image/png"
      }, {
        "src": "images/hello-icon-152.png",
        "sizes": "152x152",
        "type": "image/png"
      }, {
        "src": "images/hello-icon-192.png",
        "sizes": "192x192",
        "type": "image/png"
      }, {
        "src": "images/hello-icon-256.png",
        "sizes": "256x256",
        "type": "image/png"
      }, {
        "src": "images/hello-icon-512.png",
        "sizes": "512x512",
        "type": "image/png"
      }],
    "lang": "en-US",
    "start_url": "/index.html",
    "display": "standalone",
    "background_color": "white",
    "theme_color": "white"
}
```

最後にHTMLにも色々追加します。

```html:index.html
<head>
...
<link rel="icon" href="favicon.ico" type="image/x-icon" />  
<link rel="apple-touch-icon" href="images/hello-icon-152.png">   
<meta name="theme-color" content="white"/>  
<meta name="apple-mobile-web-app-capable" content="yes">  
<meta name="apple-mobile-web-app-status-bar-style" content="black"> 
<meta name="apple-mobile-web-app-title" content="Hello World"> 
<meta name="msapplication-TileImage" content="images/hello-icon-144.png">  
<meta name="msapplication-TileColor" content="#FFFFFF">
...
</head>
```

よし！もう一度画面を開いて、Lighthouseで評価してみましょう。
![Screen Shot 2019-10-14 at 19.58.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271670/c0669058-5257-60fb-8a12-d2223bbda0e6.png)
画面上の見た目が変わりましたね！個別のメトリクスを見るとどういう評価でしょうか。
![Screen Shot 2019-10-14 at 20.00.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271670/ee072bda-d6dc-070e-8415-97f2bc99facc.png)
おお！だいぶ緑の評価が増えています。2箇所だけ赤いままですが、これは環境上しょうがないのであきらめましょう。（一つはHTTPSじゃない！っていうのと、もう一つはviewport指定がおかしい！っていう指摘です。viewport指定をとおすにはwindow.innerWidth === window.outerWidthにすればOKだそうです。PCかつローカル環境で動作させているため、ここは許してもらいましょう。これを除けば１００点です！！）

## まとめ
これで最低限のPWA対応の指針がわかりました。実際のアプリでは何をどうキャッシュするのか、がすごく大切でかつ慎重に設計しなければならないポイントになるかと思います。localStorageはその名の通りローカルにキャッシュされていくため、色々と慎重にしないと思わぬ挙動になることもあるかもしれません。

2019年現在、主要なブラウザはほぼPWA対応がされていますし、Web技術だけでプラットフォームに依存しないネイティブっぽいアプリの実現が可能です。PWA、将来性を感じますし、面白いですね！