通知がしたいだけならPWAでいいじゃない【iOS非対応】

# 本当にあるかもしれない怖い話

作成したウェブサイトを活性化させたい、サイトの更新を知らせる仕組みがあれば活性化するはず。プッシュ通知と言えばアプリ。WebViewアプリを作りましょう。ブラウザと通知機能だけだから簡単にできるでしょ？

はい分かりましたとアプリ開発経験がなかった開発会社は安い見積を提出してしまいました。

…あなたが開発会社の窓口だとしたらどうしますか？

# アプリプロジェクトの難しさ

顧客が本当にやりたいことは、今までできたことが普通にできることに加えて **『通知をしたい』** だけなので安い見積しか通らない。それにも関わらずプロジェクトの難易度が高い点を以下にあげてみます。

## 難しさ 1. Android / iOS 両対応
コード量は大変少ないものの、Android Studio で Java(Kotlin)、Xcode で Swift も書ける要員を確保しないといけない。こんな事ができる要員はなかなかいない。

## 難しさ 2. WebView
WebView は標準のブラウザに比べて不自由なことだらけ。タブは複数開けないし、戻るや進むボタンもなければ、ピンチズームなんかも制限されてたり。
工数をかけて元のサイトより品質の悪い物を作ることになりがち。

## 難しさ 3. 開発者登録 / 開発環境
開発者登録費用(Android: 2500円くらい/初回 / iPhone: 1万円くらい/毎年)も必要、バイナリを作成して申請するのにMacも必要。

## 難しさ 4. OSバージョンアップ

OSのバージョンが上がった時に色々と不具合が出る可能性がある。
いつ上がるか分からないOSのバージョンアップのために開発要員を抱え続けられない。

# Webプッシュ

顧客が本当にやりたいことが **『通知をしたい』** だけであれば、『アプリを作る』ことは妥当でしょうか？顧客のビジネスに全く関係のないところでリスクを増やしたり、開発物に対しては妥当だが要件に対して無駄に高額な費用を請求することになっていないか？

通知をしたいだけでよければ、Webプッシュができればよいので、既存のWebサイトをPWA化することも視野に入れて良いのではないでしょうか？

※色々書きましたが、2020/9時点では、iOS は Push通知非対応の模様。対応してくれててもいいのに。顧客とよく話し合ってくださいね。

# 既存サイトの PWA (Progressive Web Apps)化

いささか大げさでしたが、PWAを知らない人に向けて『なぜ』PWAを使うのかを書いてみました。わたしも顧客との距離を縮めて妥当な提案をするべく勉強していますが、誤り等あったらご指摘頂けますと助かります。

既存のWebサイトが https のサイトであれば、いくつかのファイルを追加するだけで簡単に PWA 化できます。

1. manifest ファイル追加
1. icon ファイル追加
1. service worker ファイル追加
1. service worker 登録

https のサイトを作るのは面倒に思えますが、github のリポジトリを github pages で公開する設定にすれば、**費用も手間も不要**で簡単に作成できます。

![github-pages.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/b08d8828-ec6a-4983-8896-228f60bb5260.png)


実際に非PWAサイトをPWA化したソースコードをgithubに公開しています。よろしければ、あわせてご参照ください。

[砂漠のひつじ : github](https://github.com/kaku3/the-sheep-in-the-desert)

# PWA 構成

PWA化に際して追加したファイルは以下で、前述の通り非常に少ないです。
PWAフォルダの中に全部配置してポータビリティを上げたかったのですが、service worker 本体と manifest ファイルは、公開サイトのルートに配置する必要があるようでした。

```
manifest.json   # PWAアプリの設定。entry point など記述
sw.js           # offline でも動作可能とする service worker。
                # いくつかのイベントハンドラを記述する必要がある
pwa/
  main.js       # service worker 登録処理。定形処理。
  icons/
    icon-192x192.png    # Home Screen に配置するアイコン
    icon-512x512.png    # 起動スプラッシュアイコン
```

# manifest ファイル追加

このファイルは特に難しくはないです。
start_url には、FQDN部分以降の起動ページの path を記述します。

`https://kaku3.github.io/the-sheep-in-the-desert/` なので、
`/the-sheep-in-the-desert/index.html?utm_source=homescreen` と記述します。

``` manifest.json
{
    "name":"砂漠のひつじ",
    "short_name":"砂漠のひつじ",
    "icons": [{
        "src": "pwa/icons/icon-192x192.png",
        "sizes":"192x192",
        "type": "image/png"
      }, {
        "src": "pwa/icons/icon-512x512.png",
        "sizes":"512x512",
        "type": "image/png"
      }],
    "start_url": "/the-sheep-in-the-desert/index.html?utm_source=homescreen",
    "display": "standalone",
    "background_color": "#FFFFFF",
    "theme_color": "#FFFFFF"
}
```

また、`index.html` を修正して、manifest.json を読み込む様にします。

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

# icon ファイル追加

このファイルも特に難しくないです。
Home Screen と、起動スプラッシュ2種類用意してください。
ひょっとしたら起動スプラッシュは画像がなくても大丈夫かもしれません。

# service worker ファイル追加

簡単と書きましたが、このファイルは難しいです。
ただ書くだけなら難しくないのですが、『キャッシュするファイル』を正しく管理するには、webpack などで、キャッシュ対象ファイルのバージョンを管理できる仕組みが必要と思われます。

Service Worker の処理の記述には、[Workbox](https://developers.google.com/web/tools/workbox) を利用するのがよさそうです。


sw.js 自体は、webpack を利用していない場合は `urls` 以外の部分はそのまま利用できるかと思います。

``` sw.js
// CDN から Workbox を読み込み
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
workbox.loadModule('workbox-strategies');

const precacheController = new workbox.precaching.PrecacheController();

// キャッシュするファイルを定義する。
const baseUrl = location.href.replace(/\/sw.js$/, '');
const urls = [
    ...
    { url:'/css/fonts.css', revision: '0.01' },
    ...
].map(o => {
    o.url = baseUrl + o.url;
    return o;
})

precacheController.addToCacheList(urls);

self.addEventListener('install', (event) => {
    event.waitUntil(precacheController.install());
});
self.addEventListener('activate', (event) => {
    event.waitUntil(precacheController.activate());
});
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches
            .match(event.request)
            .then(function(response) {
                return response ? response : fetch(event.request);
            })
    );
});

```

キャッシュ容量はブラウザにより異なり、ブラウザによっては50MB程度であるためサイト内のどのファイルをキャッシュするかは設計が必要になると思います。

キャッシュは以下のような形で保存されます。

![cache.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/8a167f59-51f3-d72d-782b-19ddd88a6b53.png)


上記より分かる通り、`?__WB_REVISION__={revision}` パラメータつきでキャッシュするため、読み込みタグも同様に`?__WB_REVISION__={revision}` をつける必要があります。

``` index.html
<link rel="stylesheet" href="css/fonts.css?__WB_REVISION__=0.01">
```
``` main.css
.game {
    ...
    background-image: url("../image/bg.png?__WB_REVISION__=0.01");
    ...
}
```

読み込むファイル数にもよりますが、この部分を手動で更新するのは難しいと思いますので、実務レベルでは「パフォーマンスを確認した上で全てキャッシュしない」か「webpack」を利用するという判断になるのかなあと思います。

# service worker 登録

別ファイルにする必要もなかったのですが、再利用しやすい様に別ファイルとしました。

``` index.html
<script src="pwa/main.js"></script>
```

``` pwa/main.js
if('serviceWorker' in navigator) {
    // service worker のファイル名を変更するのであればこの行を修正。
    const serviceWorkerJs = `${location.href}sw.js`
    console.info('register service worker : ', serviceWorkerJs)
    navigator.serviceWorker.register(serviceWorkerJs)
    .then((r) => {
        console.info('Service worker registered.', r)
    })
}
```

動作させるまでには試行錯誤が必要になると思います。
https 環境ではないと動作しないとありますが、localhost については http でも大丈夫でした。
また、初回のみキャッシュされる動作をしますが、毎回キャッシュを消して確認するのが大変だったので、適当に php でサーバを立てて開発しました。

```
php -S localhost:8000
php -S localhost:8001
php -S localhost:8002
php -S localhost:8003
...
```

ローカルで動作確認が済んだら、github に push して確認してみてください。
A2HS(Add to Home Screen) ダイアログが確認できれば実装完了です。
おつかれさまでした。

![a2hs.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/b293b856-440e-5ad4-dea5-54126597b419.jpeg)

# おわりに

通知についての記事のはずでしたが、PWA実装確認までで時間切れでした。
次回は通知について書きたいと思います。

# 参考

大変分かりやすかったです。ありがとうございました。
https://qiita.com/umamichi/items/0e2b4b1c578e7335ba20