矢倉 眞隆

Google I/O 2014 ── ServiceWorker でネイティブアプリとの差を縮めよう

今回お届けするのは、Jake Archibald氏とAlex Russell氏によるServiceWorkerのセッション「Appy Times with ServiceWorker – Bridging the gap between the web and apps」です。

Alex氏はService Workers仕様のEditorで、古くはDojoやChrome Frameに携わっています。TC39やW3CのTAGのメンバーとしても活動し、Extensible Web構想を推し進める一人です。 Jake氏はService Workers仕様の「ゴーストオーサー」だそうです。とても面白い人で、今回も彼のユーモアが炸裂、笑いに包まれた楽しいセッションとなりました。

不確かなリクエストの旅を助けるServiceWorker

セッションでは「ブラウザーランド」を舞台に、ブラウザがキャッシュやインターネットからデータを取ってくるまでのやりとりを、Jake氏とAlex氏が演じて説明するという寸劇から、ServiceWorkerの紹介が行われました。

「(AppCacheの森ではなく)HTTPキャッシュに向かい、忘れっぽい魔術師に尋ねるのだ」
「魔術師が知らなかったら?ほら、忘れっぽいわけですし」
「ならば不確かさの海を渡り、子猫が戯れるインターネットに赴くのだ」
「しかし、これまでにも多くのリクエストが戻ってきていません……」

ブラウザがリソースを取得するまでには様々な不確定要因があり、遅い・繋がらないといったことはユーザーエクスペリエンスに影響します。しかし、現在のWeb標準ではそれをコントロールする術が足りておらず、Webの持つ拡張性という良さを持ってないとのこと。これを解決するものとしてApplication Cacheが提案されましたが、宣言的な記法を導入したため、見た目のシンプルさとは裏腹に、制御不能な挙動や仕様の説明不足が問題となりました(こうした既存の技術で説明不可能な挙動は、“magic”と呼ばれてたりもします)。

しかし、Application Cacheが提供する「キャッシュ」「ルーティング」「バージョン」のという概念は、Webプラットフォームにはぜひ欲しい機能です。そこでこの3機能を持ち、その上で拡張性やデバッグのしやすさを取り込んだ仕様として、ServiceWorkerが提案されました。策定にはMozillaやSamsungをはじめ、多くの開発者も加わっており、実装もChromeとMozillaが競うように進めています。

ServiceWorkerを動かす

登録・インストールで準備

ServiceWorkerはWebアプリによって登録(register)され、ServiceWorkerがインストール(install)されて待機状態となります。インストール時にはinstallイベントが発火するので、このタイミングでアプリに必要なファイルをダウンロードできます。

大活躍なES6 Promises

ServiceWorkerは複数のドキュメントから登録され、それらをコントロールします。ドキュメント主導ではなくイベント駆動であり、必要がなくなれば終了します。このため中の処理に動的なものは含められず、非同期処理が前提です。

こうした非同期処理にはコールバックやイベントなどさまざまなやり方がありますが、ServiceWorkerではECMAScript 6で定義されるPromisesというものを使います。Promisesを使うと、非同期の何かが成功した場合(resolveした)、失敗した場合(rejectされた)それぞれに対応する処理を一定のパターンで書けるため、コードの見やすさや処理漏れを防ぐことに繋がると期待できます。

Promisesについて解説すると、それだけで別な記事ができてしまうので、今回はJake氏によるPromisesに関するHTML5 Rocksの記事JavaScript Promiseの本をお読みください。

fetchでリクエスト・レスポンスに介入

待機状態のServiceWorkerは、リソースの取得(fetch)やServiceWorkerのアクティベート(activate)などのイベントが発火すると動き出します。オフラインアプリにおいては、fetch時にキャッシュの確認や更新・反映や、フォールバックの提供が考えられるでしょう。

参考:Cache APIって?

Cache APIは手軽に使えるキャッシュです。キャッシュと言ってもHTTPキャッシュとは完全に分離されており、さらには明示的な指示がない限り削除されない、ドメインを越えて共有されないといった大きな違いがあります。Cacheオブジェクトはcachesオブジェクトに複数格納できます。また、oninstall時のstatic-v1のように名前をつけて、参照しやすくもできます。

Cache APIのキャッシュは、適当なタイミングで消してやらなければいけません。ServiceWorkerは、自身の更新時にactivateイベントを発火します。このタイミングではfetchイベントが発生する前なので、キャッシュの削除はもちろん、データベースのスキーマ更新などに最適とのことです。

キャッシュを賢く使ってよいオフライン体験を

セッションでは、I/OのKeynoteで発表されたPolymerのPaper Elementsによるクイズアプリ「Topeka」に、ServiceWorkerでオフライン対応を施したアプリをデモしながら解説しました。

Topekaでは、クイズの得点を競い合うためのLeaderboard(スコア表)があります。クイズ本体はキャッシュさせたままでもオフライン動作に問題ありませんが、Leaderboardは順位が変動するのでインストール時にキャッシュさせることはできません。

というわけで、Leaderboardへのアクセスを他のアクセスと分けて処理します。URLのチェックには、URL APIを使うと少し楽です。

ここでJake氏は、Leaderboardについても常にリクエストするのではなく、キャッシュがあればまずそれを表示し、並行して最新のLeaderboardをリクエストし、取得後に反映するという、ネイティブアプリのオフライン対応でも使われる方法を提案しました。こうすると、新しいLeaderboardをリクエストしている間は画面が真っ白になるといったことがありません。

Leaderboardのキャッシュがあればそれを用い、その後で新しいLeaderboardを取得し置き換える処理を示すフローチャート

これを実装するとなると、キャッシュされているLeaderboardが最新のものかを確かめ、古い場合はキャッシュを更新しなければいけません。一体どうするのでしょうか。これはまずアプリから「キャッシュを使ってほしい」とServiceWorkerに伝えないといけません。

キャッシュを使わせるよう適当なヘッダをつけてリクエストします。ServiceWorkerはこのヘッダの有無を確かめ、キャッシュがない場合は新しいデータを取得し、キャッシュに追加します。これを処理するのが、ServiceWorkerのスクリプト側にあるleaderboardFetch関数です。

ServiceWorkerでいろいろしてみる

主にオフラインWebアプリの解決策として紹介されるServiceWorkerですが、要はクライアントサイドでプロキシを動かすようなものなので、その気になればかなりいろいろできます。

たとえば、新しい画像フォーマットを使うとして、対応してないブラウザではクライアントサイドでPNGに変換して返すなんてことができます。

こちらはもっと一般的(?)なユースケースでしょうか。あるクライアントサイドでテンプレートエンジンを動的に適用しその結果を返すといった、フレームワーク依存が強かったところも少し解決されます。

ネコ好きのJake氏は、木曜日になったらすべての画像を猫画像に差し替えてしまうコードを紹介して、拍手をもらっていました。

ServiceWorkerはキャッシュ、ルーティング、バージョニングの仕組みが分離さてているので、いろいろ応用がききますね。

また、ServiceWorkerはWeb Workersの拡張なので、postMessage()でメッセージの送受信が可能です。これを使って、「あとで読む」機能の仕組みを作れます。

普通のJavaScriptとしてデバッグ可能

ServiceWorkerのパワフルさはお分かりいただけたかと思えますが、ほかにもいろいろ利点があるとのこと。まずAppCacheとは違い、JavaScriptコードなので、DevToolsでデバッグできるという点が大きいでしょう。Chromeでは、chrome://inspect/#service-workerschrome://serviceworker-internals/といった、デバッグに便利なページも用意されています。また、現在Chrome Canaryで導入されているDevToolsの新しいエミュレーションモードでは回線状況のシミュレーションができるので、それを組み合わせると遅いネットワークを想定したテストもできるのでおすすめとのことです。このDevToolsの新しいエミュレーション機能については、Tomomi ImuraさんによるPaul Bakaus氏のセッションレポートで紹介されていますのでお読みください。

少し不便な点といえば、MitM attackを防止するためにHTTPSで提供されたページでしか動作しないことでしょうか。ただ、手元での開発への影響を抑える目的でlocalhostはその例外として機能するとのこと。

ネイティブとの差を埋めるためには

セッションのタイトルには、「Bridging the gap between the web and apps(『Webとアプリの差を埋める』)」とあります。キャッシュやルーティングによって必要なリソースをインストールさせてオフラインでも動作させるのも必要ですが、ネイティブアプリにあってWebにはまだ足りていないものがまだまだあるとのこと。その一例としてJake氏はプッシュやバックグラウンド同期を挙げ、それに対応するWeb標準(Background Sync, Push API, Web Notifications)を紹介しました。

Background Sync以外は策定中で、Notifications仕様については実装もあります。しかし、ServiceWorker対応はこれから。というわけでTopekaでどのようにServiceWorkerと同期・プッシュ・通知が実装されているかを紹介しました。Leaderboardのスコアを同期し、トップを奪われたら通知が来るという機能の実装です。

まずは同期です。ServiceWorkerがインストールされているかを確認し、スコアの同期をとります。

この同期は1回きりで終了しますが、特定の間隔で同期するタスクも登録。例として、1時間ごとに質問の更新をするタスクが紹介されました。

minIntervalはあくまでヒントとして伝えられるだけのようで、ブラウザがもっと賢くスケジューリングするのも構わないと説明していました。

さて、ユーザーが同期を許可すると、ServiceWorkerにsyncイベントが伝えられます。複数の同期をスケジュールしていたので、イベントの idプロパティで処理を振り分けます。

渡されたPromiseオブジェクトがrejectされた場合は、同期が再スケジュールされるとのことなので、また登録する必要はないとのこと。

スコアを送信する sendScore() 関数は、データベースからスコアを取得し、それをPOSTします。

プッシュメッセージについては、Google Cloud Messagingを使った実装でのやり方が紹介されました。

GCMでは、プッシュの登録が完了すると、以前の登録したプッシュの詳細が返ってくるそうで、今回処理しようとしているものと同じでないかを確認してから、サーバーにメッセージを送っています。

ServiceWorkerでは、pushイベントが通知されます。これを拾ってNotification APIで通知を出します。

デモでは2つのNexus 5を用意し高得点を競い、トップを奪われた方に通知が届きました。また、Android Wearでもその通知が表示されました。

大規模なカンファレンスにつきものなネットワークの悪さもあって、通知が来るまでに時間がかかりました。みんなそわそわしましたが、「オフラインWebアプリでも問題ないセッションが、デモのせいで失敗するとか…!」「ふー、このセッション自体も非同期とは」といったオフライン・非同期ジョークなどが飛び出し、会場を和ませました。

まだまだあるよServiceWorker

セッションで紹介されたServiceWorkerの機能は以上です。盛りだくさんでした。

このセッションでは、ServiceWorkerの更新については語られませんでした。これについてはJake氏がService Worker – first draft publishedという記事で紹介しています。

ServiceWorkerの実装は少しずつ進んでいますが、仕様の議論も並行しているため、今回紹介したコードが動かなくなる可能性は十分にあります。何かアップデートがあればまたServiceWorkerに関する記事が公開されるかもしれませんので、楽しみにしていてください。

週間PVランキング

新着記事

Powered byNTT Communications

tag list

アクセシビリティ イベント エンタープライズ デザイン ハイブリッド パフォーマンス ブラウザ プログラミング マークアップ モバイル 海外 高速化 Angular2 AngularJS Chrome Cordova CSS de:code ECMAScript Edge Firefox Google Google I/O 2014 HTML5 Conference 2013 html5j IoT JavaScript Microsoft Node.js Polymer Progressive Web Apps React Safari SkyWay TypeScript UI UX W3C W3C仕様 Webアプリ Web Components WebGL WebRTC WebSocket WebVR