WorkboxでWeb Audioのアプリをオフラインで使えるようにしてみる

この記事は WebAudio/WebMIDI API Advent Calendar 2017 Advent Calendar 2017 の21日目です。

# TL;DR
またもや、Web Audio/MIDIとあんまり関係ない記事で恐縮ですが・・・
楽器系のアプリってオンラインで使う必要がないのが多いですよね？とは言っても「ウェブアプリってオフラインで動作できるの？」みたいな疑問もあると思います。今回はそこを[Workbox](https://developers.google.com/web/tools/workbox/)というオフラインでウェブアプリを動かす為のライブラリでありビルドツールを使って細かい説明は抜きにしてチャチャッとオフライン化を試してみます。
WorkboxはServiceWorkerを使うことでウェブアプリがオフラインでも動くようにするための助けをしているのです。

# ServiceWorkerとは
ウェブページの表示、処理とは独立して動作させることのできる、開発者がJavaScriptで記述することが可能なバックグラウンドで処理を走らせることが可能な機能です。
動作する場所は、以前はブラウザがらインターネットに直接接続をしていたのですが、この間に入ることで、オフライン状態での処理だったり、ブラウザ動作していない状態でもメッセージ（Push Notification）を受けることを可能にしています。
こんな感じです。「プロキシサーバーみたいな」と説明されることも多いです。

<img width="400" alt="Screen Shot 2017-12-20 at 1.28.37 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/61221/e09c6af0-a5b4-b5b2-7769-1e0a51f829f6.png">

ちなみに、ServiceWorkerは<font color="red">HTTPS</font>のサーバのコンテンツにのみ有効です。個人で試す場合は、[GitHub Pages](https://pages.github.com/)を使うのがもっとも手っ取り早いでしょう。

## Workbox
ここで登場するのがWorkbox。ServiceWorkerってとても便利だから、もっと使ってもらうために簡単に導入できる、また最適なコードを吐き出すツールをGoogleさんが作った、という感じです。

# Service Workerでオフライン体験
## 下準備
この説明の画面が消えてしまっては困りますので、プライベートブラウジングモード(Incognito Window)を使いましょう。設定だったり、Cookieが閉じた世界になり通常のChromeのとは別扱いになります。またプライベートブラウジングモードの画面を閉じると、そこでの閲覧履歴、キャッシュは一切ブラザには残りません。（日本語モードだとちょっと見栄えが違うかもしれませんね・・・）
<img width="300" alt="Screen Shot 2017-12-20 at 12.58.48 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/61221/1a29b263-35c9-6b03-2eff-bbf5feb36755.png">


## ユーザとして体験してみる
[Get Coin!](https://ryoyakawai.github.io/getcoin/)というアプリをつくりました。アクセスしてみると、こんな画面が表示されます。ドルマークのコインをクリックすると、コインの音が鳴り（Web Audio😁）、コインがクルリと回転するだけの単純なアプリです。動作させてみましょう。
[<img width="300" alt="Screen Shot 2017-12-20 at 1.40.00 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/61221/e9836f65-e203-45d3-5262-0685dffb92ed.png" width="200px">](https://ryoyakawai.github.io/getcoin/)

さて、ここからはホントにオフラインで動作しているのか確認作業に入ります。macOSのChromeで説明しますね。
まずはDeveloper Tools(DevTools)を表示して下準備を始めます。DevToolsはメニューから[View]-[Developer]-[Developer Tools]を選択すると、下の図の右側のような画面ブラウザの中、または別のウインドウに表示されるはずです。
次に赤文字の<font color="red"><b>(1)</b></font>「Application」のタブを開き、次に<font color="red"><b>(2)</b></font>「Service Workers」を選択してください。そうすると下の図のようになると思います。
<img width="500" alt="Screen Shot 2017-12-20 at 1.46.26 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/61221/c7a3d8c4-ad79-a0c9-3358-c87bee01d3ac.png">

では早速オフラインにしまみましょう。<font color="red"><b>(3)</b></font>の「Offline」にチェックを入れてください。これでブラウザはオフラインになりました。[Google.co.jp](google.co.jp)だったり[Yahoo.co.jp](yahoo.co.jp)にこのタブでアクセスしてみてください。みんなの大好きなOffline Dinosauarに会えたことでしょう。
<img width="416" alt="Screen Shot 2017-12-20 at 1.58.59 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/61221/d35514b1-a1e7-3130-ff36-6ca405d41a7d.png">
この状態でもう一度[Get Coin!](https://ryoyakawai.github.io/getcoin/)にアクセスしてみてください。不思議なことにOffline Dinosauarには会えなく、Get Coin!のアプリがオンラインのときと同様に動作したはずです。

ということで、めでたくウェブアプリでもオフラインで動作することが体験できました！

## 開発者として体験してみる
（この先を体験するには[Node.js](https://nodejs.org/)がインストールされている必要があります。Node.jsのVersionは`9.3.0`を使っています。またTerminalをご用意ください。）

### コードをCloneする
体験する為にコードを用意しましたので`Clone`してください。[[GitHub]](https://github.com/ryoyakawai/getcoin)

````shell
$ git clone https://github.com/ryoyakawai/getcoin.git;
````

### npmパッケージのインストール
続いて、動作に必要なパッケージをnpmを使ってインストールします。

````shell
$ cd getcoin;
$ npm install;
````

### オンラインのみで動作するモードでビルド
そして、オンラインのみで動作するモードでビルドします。

````shell
$ npm run build:nosw;
````

### ウェブサーバを起動して動作の確認
ウェブサーバを起動します。

````shell
$ npm run start;
````

プライベートモードのChromeで`http://localhost:8081`にアクセスしてみてください。Get Coinが無事に表示されたと思います。
続いて、オフラインでは動かないことを確認。
Terminal画面に戻って、`Ctrl` + `c`とかでウェブサーバを停止し、ブラウザを更新すると、、、Get Coinは動作しません。

これで、オフラインでは動かないことが確認できました。
続いてオフラインでも動作するモードでビルドします。

<a href="#オフラインでも動作するモードでビルド"></a>
## オフラインでも動作するモードでビルド

````shell
$ npm run build:sw;
````

すると3つの質問がWorkboxからされます。オフライン動作をさせる為に必要な情報を収集する目的の質問です。（「workbox-cliは動かなくなるよ」という警告を気に留めておきましょう・・・）
1つ目は`src`を選択してください。
（ビルド前のソースのありかを問われています）

````shell
$ npm run build:sw;

> coin@1.0.0 build:sw /Users/kawai/Y_UX/Sources/tmp/coin.master/getcoin
> workbox-cli generate:sw && copyfiles -e '**/sw.js' -u 1 src/*.{html,js,css,json,png} build

Please run the command line tool as 'workbox' instead of 'workbox-cli'.
'workbox-cli' will stop working in the next major release.
(See: https://github.com/GoogleChrome/workbox/issues/730)
? What is the root of your web app? (Use arrow keys)
  build 
❯ src 
  ──────────────
  Manually Enter Path 
````

2つ目の質問は何もせずにそのまま`Enter`キーで次に進みます。
（どのファイルタイプをキャッシュするか、すなわちオフィラインで使いたいか、を問われています。ファイルタイプで選択するの？という疑問はさておき・・・）

````shell
$ npm run build:sw;

> coin@1.0.0 build:sw /Users/kawai/Y_UX/Sources/tmp/coin.master/getcoin
> workbox-cli generate:sw && copyfiles -e '**/sw.js' -u 1 src/*.{html,js,css,json,png} build

Please run the command line tool as 'workbox' instead of 'workbox-cli'.
'workbox-cli' will stop working in the next major release.
(See: https://github.com/GoogleChrome/workbox/issues/730)
? What is the root of your web app? src
? Which file types would you like to cache? (Press <space> to select, <a> to toggle all, <i> to inverse selection)
❯◉ css
 ◉ js
 ◉ png
 ◉ html
 ◉ json
````

3つ目の質問も、何もせずにそのまま`Enter`キーで次に進みます。

````shell
$ npm run build:sw;

> coin@1.0.0 build:sw /Users/kawai/Y_UX/Sources/tmp/coin.master/getcoin
> workbox-cli generate:sw && copyfiles -e '**/sw.js' -u 1 src/*.{html,js,css,json,png} build

Please run the command line tool as 'workbox' instead of 'workbox-cli'.
'workbox-cli' will stop working in the next major release.
(See: https://github.com/GoogleChrome/workbox/issues/730)
? What is the root of your web app? src
? Which file types would you like to cache? (Press <space> to select, <a> to toggle all, <i> to inverse selection)css, js, png, html, json
? What should the path of your new service worker file be (i.e. './build/sw.js')? (build/sw.js) 
````

サイトに、質問の答えを保存するかを問われます。保存すると次回からはこれらの質問には答えなくて良くなりますが、ここでは保存しない`n`を選択します。

````shell
$ npm run build:sw;

> coin@1.0.0 build:sw /Users/kawai/Y_UX/Sources/tmp/coin.master/getcoin
> workbox-cli generate:sw && copyfiles -e '**/sw.js' -u 1 src/*.{html,js,css,json,png} build

Please run the command line tool as 'workbox' instead of 'workbox-cli'.
'workbox-cli' will stop working in the next major release.
(See: https://github.com/GoogleChrome/workbox/issues/730)
? What is the root of your web app? src
? Which file types would you like to cache? (Press <space> to select, <a> to toggle all, <i> to inverse selection)css, js, png, html, json
? What should the path of your new service worker file be (i.e. './build/sw.js')? build/sw.js
? Last Question - Would you like to save these settings to a config file? (Y/n) n
````

以上で準備は完了です。

### ウェブサーバを起動して動作の確認
ウェブサーバを起動し、先ほどと同様にプライベートブラウジングモードのウィンドウで`http://localhost:8081`を表示し、Get Coinの動作を確認してみましょう。

````shell
$ npm run start

> coin@1.0.0 start /Users/kawai/Y_UX/Sources/tmp/coin.master/getcoin
> node server.js

App listening at http://localhost:8081
````

動作したはずです。では、オフラインで動くか試してみます。

### オフラインでも動作するか確認
こちらも先程同様に、Terminal画面に戻って、`Ctrl` + `c`とかでウェブサーバを停止し、ブラウザを更新すると先程は動かなかったGet Coinが動作・・・・しました！
これで、自分の環境でもオフラインのウェブアプリが開発できることが分かりました。


## 簡単な説明
ビルド前のソースのある`src`ディレクトリ内のファイルと、オフラインでも動作するようにビルドした後の`build`ディレクトリの比較をしてみると以下の3つがbuildディレクトリ内にしかないことが分かります。

- sw.js
- workbox-sw.prod.v2.1.2.js
- workbox-sw.prod.v2.1.2.js.map

`sw.js`が怪しいですね。残り2つはWorkboxのライブラリであることは想像がつきます。`sw.js`の中身を見てみます。

````javascript
const fileManifest = [
  {
    "url": "coin.css",
    "revision": "50717a84d6e367e4f08ac172945a3892"
  },
  {
    "url": "coin.js",
    "revision": "aed0df59696b46148080cd379d320460"
  },
  {
    "url": "coinicon_144x144.png",
    "revision": "3edc5d3a4321edce9a67bd5fab48f079"
  },
  {
    "url": "coinicon_200x200.png",
    "revision": "de2f6354cc4cf6943d8bc2557cab3dd6"
  },
  {
    "url": "index.html",
    "revision": "0cf5edd8967448b09d44c2e744c98f7c"
  },
  {
    "url": "main.css",
    "revision": "0cbc865c7c504e0117b0c77b8613e8a6"
  },
  {
    "url": "manifest.json",
    "revision": "cc04baae268df20734344d542b7bdfb6"
  }
];

const workboxSW = new self.WorkboxSW();
workboxSW.precache(fileManifest);
````

`url`と`revision`を持ったObjectの配列として書かれていました。これは、どのファイルをキャッシュするかのが`url`で、またどのVersionとしてキャッシュするかが`revision`で書かれています。この`revision`は、更新したファイルを動作させてときにキャッシュし直しが必要かどうかの管理をしていますので、重要です。ここが書き換えられないと、ウェブサーバ側でファイルが更新されても、ブラウザに反映されない、ということが起こることになります。
`sw.js`は、Workboxからの質問に答えることで生成されました。ウェブサーバ側でファイルが更新したときは、Workboxからの質問に答えて再生成してください。（他にも方法あるかも？）

ということでウェブアプリをオフライン化することが簡単にできました。

## 自分で作ってみる
すぐに始められるように`git clone`したソースに[`template`](https://github.com/ryoyakawai/getcoin/tree/master/template)以下に雛形を作りました。ここの下にウェブアプリを作り「[オフラインでも動作するモードでビルド](https://qiita.com/ryoyakawai/items/a475b9f97cb8e558b76a#%E3%82%AA%E3%83%95%E3%83%A9%E3%82%A4%E3%83%B3%E3%81%A7%E3%82%82%E5%8B%95%E4%BD%9C%E3%81%99%E3%82%8B%E3%83%A2%E3%83%BC%E3%83%89%E3%81%A7%E3%83%93%E3%83%AB%E3%83%89) 」からの手順を踏むとオフラインで動作するアプリを作ることが可能です。是非試してみてくださいね！

# まとめ
ウェブアプリのオフライン化が可能であること、また比較的に簡単に実現できることを確認してもらえたと思います。オンラインである必要が特にないウェブアプリでは使うことをオススメします。がんばって開発したアプリケーションを多くの人に使ってもらう為にも是非導入してみてください！

# 補足 (2017/12/21 利用可能なブラウザについて説明追記)
2017年12月21日現在の利用可能なブラウザについては以下の表の通りです。 [現状はこうなっています。](https://caniuse.com/#search=service%20worker)
<img width="750" alt="Screen Shot 2017-12-20 at 11.02.28 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/61221/9a31dc35-3ac6-42f0-49eb-c66c677dfabd.png">