サイゼリヤ1000円ガチャでPWAに入門してみた

この記事は[PWA Advent Calendar 2020](https://qiita.com/advent-calendar/2020/pwa)の11日目の記事です。
不動産スタートアップ [株式会社estie](https://www.estie.co.jp/)でデータエンジニアやっています、🦊 marushoです。

# 🍳 はじめに

業務と関係のない技術を触ってみたくなることありませんか？ ありますよね〜

この記事は「データエンジニアの僕が初めてPWAを触ってみた」ときの備忘録となります。

## 🐥 きっかけ
昨年、なにか面白そうな本ないかな〜と[技術書典7](https://techbookfest.org/event/tbf07)に出向いたときに
[みんなで叩いてみた PWAへの入門の扉](https://booth.pm/ja/items/1592221) という書籍に出会いました。

実は以前、[サイゼリヤ1000円ガチャ](https://qiita.com/marusho_summers/items/a2d3681fac863734ec8a)というジョークWebサイトを作ったところ
「PWAにはしないのですか？」と質問を頂いたことがあります。

「PWAって何...?」という状態でした。

上記の本では、同じように「PWAって何?」と思っていたエンジニアの方々がPWAに入門してみた本で
「PWAってどういうもの？」というところから、実装まで優しく解説してくれています。

これを読んでPWAにするぞ〜と思ってから、1年の積読期間を経て
サイゼリヤ1000円ガチャをPWAにしてみようと思います！

PWAへの入門の扉、叩けるだろうか...

# 📖 PWAって何？

[MDN](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps)にはこう書かれていました
>プログレッシブウェブアプリ(PWA) は、新しいウェブ API と伝統的なプログレッシブな拡張戦略を使用して、クロスプラットフォームのウェブアプリケーションにネイティブアプリと同様の使い勝手をもたらすウェブアプリのことです。

実際に触ってみるのがイメージできるかと思います。
[日本経済新聞](https://www.nikkei.com/)のサイトをホーム画面に追加してからアクセスしてみると
あら不思議、アプリのようなUIになります。

Webサイトでありながら、ネイティブアプリのような挙動を再現でき
キャッシュによって高速化&オフライン動作&プッシュ通知を実現しています。
なかなか面白い技術ですね〜

# 🍝 できたもの 

いきなりアウトプットですが
サイゼリヤ1000円ガチャが、PWAになりました 🎉

https://saizeriya-1000yen.marusho.io/
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208648/60ddc1c0-8b65-194e-1b5f-cffda03c3514.png" width=30%>
見た目はそのままですが、設定を開いて...
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208648/77687c76-faa4-4865-e947-2b3a00197c70.png" width=30%>
ホーム画面に追加して...
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208648/ab5115e4-b96b-6455-62a0-867ba557cd26.png" width=30%>
アイコンをタップすると...
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208648/ae4b295d-578a-af69-7790-6fe4bfd8b6f8.png" width=30%>
全画面に表示されてアプリのようなUIになります👌

PCにも追加することができます
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208648/b87272f1-4112-d124-d35f-8211138b115a.png" width=30%>
ブラウザの右上にある + を押すと、
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208648/1679745e-c19b-fcfb-4b78-86d042799b2d.png" width=30%>
デスクトップアプリのように動作します

2020/12/09 のグランドメニューにも対応したので、ぜひ遊んでみてください🍗

# ⛰ PWAにするまで

最後にGithubでコードを公開してます！

## 🔥 キャッシュで動作するように書き換える

PWAのメリットを活かしてキャッシュを使ってオフラインで動作するようにしたいので
sqliteで持っていたメニューデータをJSON化/フロントのみで動作するように書き換えました。

サイゼリヤのメニューは選び抜かれているので、全て合わせても130種類ほどです。
JSONで20kB程度で、このデータをキャッシュさせることでオフラインでも動作するようにします。

##### 参考
元々メニューデータはsqLiteのdbファイルとして持っていたため、JSONも同じように扱いたいなーと思い
JSONをSQLで扱える[AlaSQL](http://alasql.org/)というライブラリを使用しました。
`MENU`にJSON入れることで以下のようにDBにクエリを投げる感覚でデータを取得できます。


```javascript

var sql = 'SELECT * FROM ? d WHERE d.price <= ?';
var candidates = alasql(sql, [MENU, budget]); //メニューから特定の値段以下の商品のみを抽出
```

## 👝 PWAに必要なファイルを用意する 

PWAにするには4つのポイントがあるようです。

1. HTTPSに対応している
2. `service-worker.js`を用意する
3. `manifest.json`を用意する
4. iOSで全画面対応にするには`<meta>`の設定をする

### 1.HTTPSに対応している

PWAとして認識される条件にHTTPS対応されていることがあります。
HerokuもHTTPS対応となりますが、今回は手軽に[Github Pages](https://pages.github.com/)でホストすることにしました。

### 2.`service-worker.js`を用意する

PWAの要のひとつであるService workerです。
install時にどのファイルをキャッシュするか、Push通知を受け取って表示するなどの制御を行います。
概要を掴むのは[こちらの記事](https://webtan.impress.co.jp/e/2019/09/02/33637)が分かり易かったです。

今回行いたいのは

- インストール時にオフライン動作に必要なファイルのキャッシュする
- オフライン優先で動作
- サイトが更新された場合はキャッシュを更新する

なので、以下のような`service-worker.js`を用意しました。

```service-worker.js
// cache name, cache files
var CACHE_NAME = 'saizeriya-cache-v1';
var urlsToCache = [
    '/index.html',
    '/static/style.css',
    '/gacha.js',
    '/menu.json.js'
];

// install cache
self.addEventListener('install', function (event) {
    event.waitUntil(
        caches
        .open(CACHE_NAME)
        .then(function (cache) {
            return cache.addAll(urlsToCache);
        })
    );
});

// use cache
self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.open(CACHE_NAME).then(function (cache) {
            return cache.match(event.request).then(function (response) {
                return response || fetch(event.request).then(function (response) {
                    return caches.open(CACHE_NAME).then(function (cache) {
                        cache.put(event.request, response.clone());
                        return response;
                    });
                });
            });
        })
    );
});

// refresh cache
self.addEventListener('activate', function (event) {
    event.waitUntil(
        caches.keys().then(function (cacheNames) {
            return Promise.all(
                cacheNames.filter(function (cacheName) {
                    return cacheName !== CACHE_NAME;
                }).map(function (cacheName) {
                    return caches.delete(cacheName);
                })
            );
        }).then(function () {
            clients.claim();
        })
    );
});


```

また、ServiceWorkerを登録するために`index.html`にregisterを仕込みます。

```index.html
<script>
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./service-worker.js').then(function () { console.log('Service Worker Registered'); });
    }
</script>
```

### 3. `manifest.json`を用意する

[manifestファイル](https://developer.mozilla.org/ja/docs/Web/Manifest)では、Webサイトに関する情報をJSON形式で設定します。
アプリ名や表示方法などを設定することができます。

色々設定できるようですが、今回は以下を設定しました。

- `name` : ウェブアプリの名前
- `short_name` : ホーム画面に表示する名前
- `display` : 表示方法 (全画面・横向き固定など)
- `start_url` : アプリの起動時のアクセス先
- `icons` : アイコン情報

```manifest.json
{
    "name": "Saizeriya1000yen-gacha",
    "short_name": "Saizeriya1000",
    "display": "standalone",
    "start_url": "/",
    "icons": [
        {
            "src": "static/apple-touch-icon.png",
            "sizes": "192x192",
            "type": "image/png"
        }
    ]
}

```

### 4. iOS対応をする

こちら少しハマったポイントです。
iOS Safariでは`manifest.json`の設定を読み込めないため
少し工夫が必要です。

調べたところ、2つの方法があるようです。

#### a. metaタグを入れる

全画面表示やアイコンの設定をhtmlに`meta`タグとして直接仕込んでしまう方法です。

```index.html
<meta name="apple-mobile-web-app-title" content="1000円ガチャ">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" sizes="180x180" href="static/apple-touch-icon.png">　
```

#### b. [PWA Compat](https://developers.google.com/web/updates/2018/07/pwacompat)を使う

[PWA Compat](https://developers.google.com/web/updates/2018/07/pwacompat)を使うと、manifestファイルを読み込んで`link`や`meta`を生成してくれます。
今回はこちらを採用しました。

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

...
<script async src="https://cdn.jsdelivr.net/npm/pwacompat" crossorigin="anonymous"></script>
```

## 🚀 デプロイ

[Github Pages](https://pages.github.com/)でまるっとデプロイしてしまいます。
HTTPS対応でリポジトリにpushするだけでよいので、とても便利・・・

今回作成したコードの全体はコチラ↓
https://github.com/marushosummers/Saizeriya_1000yen_pwa

# 🍵 おわりに

無事、入門の扉を叩けました...！

まだPWAの全体像は掴めていませんが
ちょっとした設定でWebアプリがネイティブアプリっぽくなるのは、とても魅力的です。

まだPWA自体は途上の部分があるようですが
個人制作のWebサイトやブラウザゲームをPWAにする範囲では
一気にアプリ感が出ておトクな気分になりますね。

皆さんもぜひ、PWAを触ってみてください！

# ☕️ さいごに

僕が所属する[estie](https://www.estie.co.jp/)では、Python/JavaScript/Rubyエンジニアを積極採用中です！

不動産領域でのプロダクト・データ基盤開発に興味のある方はぜひ、
[estie inside blog](https://inside.estie.co.jp/)や[採用ページ](https://www.estie.co.jp/#recruit)をご覧ください。

Twitterでお気軽なご連絡もお待ちしています〜！

[:office: estie](https://twitter.com/estiejp)  [🦊 marusho](https://twitter.com/marusho_summers)

# 📚 参考リンク

- [MDN](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps)
- [みんなで叩いてみた PWAへの入門の扉](https://booth.pm/ja/items/1592221)
- [【PWA】シリーズPWA (5) キャッシュを制御して高速化やオフライン対応をしてみる](http://shinimae.hatenablog.com/entry/2019/08/10/155502)

#### 😄 参考にさせていただきました !
- [吉野家1000円ガチャをJavaScriptで作ってみた．](https://note.com/akirahiraoka/n/nce26beb3a440)
- [サイゼリヤ1000円ガチャに触発されてサイゼリヤN円ガチャを作りました(Nuxt.js, Vuetify)](https://qiita.com/harhogefoo/items/348d89164c9c1bc91f08)