AMPページをPWA化する。AMPでServiceWorkerを使う方法

AMP縛りでページ作ったけど、やっぱりPWA化もしたいと思いやってみたらあまりにも簡単にできたので手順を残しておきます。
出来上がったページを[Lighthouse](https://developers.google.com/web/tools/lighthouse/?hl=ja)で計測した結果、PWAの点数は100点満点でした。

# TL;DR
* [Web App Manifest Generator](https://app-manifest.firebaseapp.com/)使ってmanifest.jsonを生成
* [amp-install-serviceworker](https://www.ampproject.org/docs/reference/components/amp-install-serviceworker)でAMPでもService Worker動く
* [workbox](https://developers.google.com/web/tools/workbox/)でService Workerコードを簡単に生成

# AMP(Accelerated Mobile Pages)とは
* モバイルページを高速化するためのもの
* 速くするというより遅くしない
* 詳しくは[ampproject.org](https://www.ampproject.org/ja/learn/overview/)を見て下さい

# PWA(Progressive Web Apps)とは
* Webとネイティブアプリの良いところを合わせてUXを良くしようとする仕組み
* オフライン、プッシュ通知、ホーム画面から起動などネイティブアプリっぽいことをWebで実現
* 詳しくは[GoogleのPWAページ](https://developers.google.com/web/progressive-web-apps/?hl=ja)を見て下さい


# AMPの実装
最初にAMPページを用意します。
既に手元にPWA化したいAMPページがある方はここは読み飛ばしてください。
AMPページが無い方は、[初めての AMP ページを作成する](https:おｒｋ//www.ampproject.org/ja/docs/getting_started/create)で作ったAMPページをもとに進めます。
AMP HTML の実装は以下の通りです。
コピペして画像のパスを変更してもらえればAMPページはすぐできます。

```html:index.html
<!doctype html>
<html amp lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello, AMPs</title>
    <link rel="canonical" href="http://example.ampproject.org/article-metadata.html">
    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
    <script type="application/ld+json">
      {
        "@context": "http://schema.org",
        "@type": "NewArticle",
        "headline": "Open-source framework for publishing content",
        "datePublished": "2015-10-07T12:02:41Z",
        "image": [
          "logo.png"
        ]
      }
    </script>
    <style amp-custom>
      /* any custom style goes here */
      body {
        background-color: white;
      }
      amp-img {
        background-color: gray;
        border: 1px solid black;
      }
    </style>
    <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
    <script async src="https://cdn.ampproject.org/v0.js"></script>
  </head>
  <body>
    <h1>Welcome to the mobile web</h1>
    <amp-img src="welcome.png" alt="Welcome" height="400" width="800"></amp-img>
  </body>
</html>
```

## 有効なAMPページかテストする
この時点でGitHub PagesやNetlifyにホスティングして、[AMPテスト](https://search.google.com/test/amp)ページで有効なAMPか確認します。
有効な場合、以下のような画面が表示されます。

<img width="732" alt="スクリーンショット 2018-03-19 00.18.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/93166/181e3f53-ddfa-e936-bad3-f47750ca8a88.png">


# PWA対応する
AMPページをPWA化していきます。
やることは以下のとおりです。

* manifest.jsonを用意する
* Service Workerを導入する


## manifest.jsonを用意する
ただのJSONなので手で書いても時間はかかりませんが、はじめて書く方やもっと楽したい人のために[Web App Manifest Generator](https://app-manifest.firebaseapp.com/)を使います。

https://app-manifest.firebaseapp.com/ にアクセスすると以下の画面に飛びます。

<img width="1493" alt="スクリーンショット 2018-03-19 00.35.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/93166/a576d0a5-fcef-282c-40b8-533535dd1431.png">


各項目についてはGoogleが公開している[ウェブアプリ マニフェスト](https://developers.google.com/web/fundamentals/web-app-manifest/?hl=ja)を見て下さい

青い「ICON」ボタンをクリックしてホーム画面に使うアイコン画像を選択します。
「GENERATE ZIP」ボタンをクリックしてapp-images.zipというzipファイルをダウンロードします。
このzipファイルにはmanifest.jsonと様々なサイズの画像が梱包されています。
zipを解凍してmanifest.jsonとアイコンの画像をプロジェクト内に置きます。
ダウンロードしたmanifest.jsonは以下のようになっていると思います。
内容を変更する場合は手動で書き直してください。

```json:manifest.json
{
  "name": "AMP PWA Sample App",
  "short_name": "Sample",
  "theme_color": "#2196f3",
  "background_color": "#2196f3",
  "display": "standalone",
  "Scope": "/",
  "start_url": "https://example.com/amp-pwa/",
  "icons": [
    {
      "src": "images/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "splash_pages": null
}
```

### HTMLからmanifest.jsonを読み込む
以下のように`head`タグにmanifest.jsonを読み込む記述を追加します

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

### metaタグtheme-colorを設定する
完全なPWAにするにはmanifest.jsonで指定したtheme-colorをHTML内にmetaタグで追加する必要があります。
contentの値はmanifest.jsonで定義したtheme-colorの値に合わせてください。

```html:index.html
<meta name="theme-color" content="#2196f3">
```

これでmanifest.jsonの用意は完了です。

## Service Workerを導入する
Service Workerについては[Service Workerの紹介](https://developers.google.com/web/fundamentals/primers/service-workers/?hl=ja)や[Service Workerの基本とそれを使ってできること](https://qiita.com/y_fujieda/items/f9e765ac9d89ba241154)をお読み下さい。

AMPページではJavaScriptの実行に制限がありますが、amp-install-serviceworkerを使うことでService Workerを使うことができます。
また、~~sw-precache~~[workbox](https://developers.google.com/web/tools/workbox/)でService Workerのコードを簡単に自動生成することができるのでService Workerのための学習は特に必要なく導入が可能です。

### amp-install-serviceworkerを使ってService Workerを利用可能にする
AMPページでServiceWorkerを使う場合、[amp-install-serviceworker](https://www.ampproject.org/docs/reference/components/amp-install-serviceworker)を使うことになります。
`head`の中に以下の`script`タグを追加してください。

```html:index.html
<script async custom-element="amp-install-serviceworker"
  src="https://cdn.ampproject.org/v0/amp-install-serviceworker-0.1.js"></script>
```

次に`body`の中に以下の`amp-install-serviceworker`タグを追加してください。
`src`はこの後紹介するService Workerのコードのファイルパスを設定します。
`layout`は"nodisplay"を設定します。
`data-iframe-src`はGoogle検索からアクセスする際にこの属性が無いとServiceWorkerは正常に動作しません。これについては後述します。
その他の設定値に関しては[amp-install-serviceworkerのリファレンス](https://www.ampproject.org/docs/reference/components/amp-install-serviceworker)にてご確認ください。

```html:index.html
<amp-install-serviceworker
  src="./sw.js"
  layout="nodisplay"
  data-iframe-src="https://your_site_domain/sw.html">
</amp-install-serviceworker>
```

data-iframe-srcについて補足します。
Google検索からのアクセスした場合、AMPページはAMP CDNから返されます。このドメインはwww.google.co.jpです。AMPページのURLを見るとわかるのですが、 https://www.google.co.jp/amp/s/your_domain/ となっているはずです。
JavaScriptファイルの取得先のドメインはデプロイした先のドメインです。例えばGitHub Pagesにデプロイしていたらusername.github.ioとなります。
ServiceWorkerの仕様としてJavaScriptファイルが違うドメインの場合、ServiceWorkerへの登録は行なえません。
そのためJavaScriptの取得先と同じドメインにHTMLを用意して、そのHTML内でServiceWorkerへの登録を行います。
`data-iframe-src`はそのServiceWorkerの登録を行うHTMLのURLを指定するための属性です。
以下が`data-iframe-src`に指定するHTMLのサンプルです。

```html:sw.html
<!DOCTYPE html>
<html>
  <head>
    <title>installing Service Woker</title>
    <script type="text/javascript">
        if("serviceWorker" in navigator) {
          navigator.serviceWorker.register("/sw.js")
            .then(function(reg){
              console.log('SW scope: ', reg.scope);
            })
            .catch(function(err) {
              console.log('SW registration failed: ', err);
            });
        };
    </script>
  </head>
  <body>
  </body>
</html>
```



これでAMPページ内でService Workerを使う準備が整いました。

### workboxを使ってService Workerのコードを生成する
[workbox](https://developers.google.com/web/tools/workbox/)を使うとService Workerのコードを簡単に生成することができます。

workboxはCLI、node、webpackのいずれかで使うことができます。
今回は最も簡単そうなCLIで使います。CLIで使うには[workbox-cli](https://developers.google.com/web/tools/workbox/modules/workbox-cli)を使います。
npmで配布されているので、以下のようにインストールすることも可能ですが、

```bash
npm install -g workbox-cli
```

今回は一度しか使わないため[npx](https://www.npmjs.com/package/npx)を使って実行します。
[npx](https://www.npmjs.com/package/npx)はグローバルにインストールせずにダウンロードと実行を一発で行ってくれます。

workbox-cliには4つのモードがあります。
* wizard
* generateSW
* injectManifest
* copyLibraries

今回はwizardを使います。4つのモードについて詳しくは[workbox-cli](https://developers.google.com/web/tools/workbox/modules/workbox-cli)をご確認お願い致します。


```bash
npx workbox-cli wizard
```

実行するといくつか質問が表示されるので、答えていきます。
すべて答えると`workbox-config.js`というファイルが自動生成されます。
<img width="873" alt="スクリーンショット 2018-03-26 00.15.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/93166/910531e5-bc91-9a0f-5f77-a534073e6388.png">

自動生成された`workbox-config.js`はローカルのファイルをキャッシュする設定はwizardで選択するだけで設定できるのですが、今回は`ampproject.org`のCDNから取得したJSファイルもキャッシュしなければオフライン対応ができません。
そのため`workbox-config.js`を以下のように修正します。

```js:workbox-config.js
module.exports = {
  "globDirectory": ".",
  "globPatterns": [
    "**/*.{png,html,json,js}"
  ],
  "swDest": "sw.js",
  // 以下を手動で追加
  "runtimeCaching": [{
    urlPattern: new RegExp('^https://cdn.ampproject.org'),
    handler: 'staleWhileRevalidate',
    options: {
      cacheableResponse: {
        statuses: [0, 200]
      }
    }
  }]
};
```

修正した`workbox-config.js`を元にService Workerのコードを生成します。
以下のコマンドを実行してください。

```bash
npx workbox-cli generateSW workbox-config.js
```

コマンドを実行するとプロジェクト直下に`sw.js`というファイルができているはずです。
この`sw.js`がService Workerのコードです！

## プロジェクト構成
ここまでで以下のようなプロジェクトになっているかと思います。

```
.
├── images
│   └── icons
│       ├── icon-128x128.png
│       ├── icon-144x144.png
│       ├── icon-152x152.png
│       ├── icon-192x192.png
│       ├── icon-384x384.png
│       ├── icon-512x512.png
│       ├── icon-72x72.png
│       └── icon-96x96.png
├── index.html           
├── manifest.json
├── sw.js
├── welcome.png
└── workbox-config.js
```


## Lighthouseで確認する

最後にPWA化できているのか確認してみましょう。
GitHub PagesとかNetlifyなどにホスティングして[Lighthouse](https://developers.google.com/web/tools/lighthouse/?hl=ja)で確認すると満点に近い点数になってるはずです。
自分はGitHub PagesにホスティングしてChrome DevToolsのAuditsで確認し100点でした。

<img width="711" alt="スクリーンショット 2018-03-19 00.02.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/93166/ffe29e7f-f8b6-e750-1a68-3084e73941a9.png">

# まとめ
* [Web App Manifest Generator](https://app-manifest.firebaseapp.com/)使えばmanifest.jsonとアイコンも生成できる
* [amp-install-serviceworker](https://www.ampproject.org/docs/reference/components/amp-install-serviceworker)と[workbox](https://developers.google.com/web/tools/workbox/)使えばさくっとAMPページにService Worker導入できる
* AMPページは簡単にPWA化できる

# 参考
* [ampproject.org](https://www.ampproject.org/ja/learn/overview/)
* [Progressive Web Apps](https://developers.google.com/web/progressive-web-apps/?hl=ja)
* [初めての AMP ページを作成する](https://www.ampproject.org/ja/docs/getting_started/create)
* [Progressice Web AMPs](https://codelabs.developers.google.com/codelabs/amp-in-pwa/#0)
* [amp-install-serviceworker](https://www.ampproject.org/docs/reference/components/amp-install-serviceworker)
* [sw-precache](https://github.com/GoogleChromeLabs/sw-precache)
* [workbox](https://developers.google.com/web/tools/workbox/)
* [Web App Manifest Generator](https://app-manifest.firebaseapp.com/)


最後までお読み頂きありがとうございました。
不備や質問はコメント欄または[Twitter](https://twitter.com/shisama_)にてお願い致します。


# 2018/03/26 修正
この記事を最初に書いたときは[sw-precache](https://github.com/GoogleChromeLabs/sw-precache)を使った説明でしたが、開発が止まっているっぽく、後継にあたる[workbox](https://developers.google.com/web/tools/workbox/)を使った説明に修正しました。