今月上旬5月8日に、W3CよりServiceWorkerの草案初版が提示されました。ServiceWorkerは、オフラインWebアプリケーションの開発者が問題と考える点を解決する、非常に魅力的な仕様です。日本語の情報がほとんどないこのタイミングで、HTML5 Expert.jp編集部が解説いたします!
ServiceWorkerとは
ServiceWorkerは、リソースの永続的なキャッシュを可能にする、およびWebアプリケーションのリソース要求の処理を可能にする新しい機能です。Webページを開く前であっても(ネットワークの接続/切断の有無にかかわらず)、独自の処理を挟み込めるのがポイントです。クライアント側に、一種のプロキシサーバがあるようにイメージされると分かりやすいと思います。
ServiceWorker自体は、WebWorker同様にJavaScriptで記述し、特にWebWorkerの一種であるSharedWorkerに似ています。最大の違いとしては、SharedWorkerはWebページによってコントロールされるのに対し、ServiceWorkerはWebページをコントロールします。任意のWebアプリケーション(ServiceWorker)がひとたびインストールされると、次回以降のアクセスでは、ブラウザ側の処理がはじまる前に、ServiceWorkerの処理が起動します。
注意点(ブラウザのサポート状況について)
執筆時点では、ServiceWorkerをサポートするブラウザはありません。Chrome Canaryでは、以下のようにchrome://flagsにて一部の機能を有効化できますが、あくまで一部の機能であり、後述するキャッシュからリソースを取得する機能等、多くの機能は動作しません。
登場の背景
オフラインWebアプリケーションの開発において、よく知られるキャッシュ機能にApplicationCacheがあります。ApplicationCacheは、ネットワークが切断されている状態であっても、事前にキャッシュしたコンテンツを、Webアプリケーションに提供しますが、こちらの記事でも言及されているとおり、様々な問題を抱えています。
例えば、ApplictionCacheは一部のキャッシュのみを更新することができません。ApplicationCacheでは、キャッシュしたいリソースを以下のようなmanifestファイルにすべて記載しておく必要があり、
CACHE MANIFEST index.html stylesheet.css yourapp.js myimage.png
ブラウザがWebページにアクセスした際に、これらのリソースはすべてダウンロードされます。仮に上記のコメント箇所のように、リソースの一部のキャッシュのみを更新したいと考えていても、不可能となります。
上記の例は、ApplicationCacheの抱える問題点の一例に過ぎず、その他にも問題点が多くあります。ServiceWorkerは、これらの問題点に対する解決方法となります。
以下で、W3Cの草案で示されるコードサンプル(本記事では一部簡略化)を参考に、利用イメージを解説します。W3Cの例では、非同期処理にpromiseを利用しています。本記事ではpromiseの解説はいたしませんので、必要に応じてMDN等を参照ください。なお、前述した通りで、執筆時点でServiceWorkerが動作するブラウザはありませんので、以下のコードは実際には動作しない点にご注意ください。
ServiceWorkerの利用イメージ
例えば、https://www.example.com/index.htmlにて、ServiceWorkerを利用するケースを考えます。(ServiceWorkerはセキュリティの都合上、HTTPSが必須となる点に注意ください) 先に処理フローを図示し、その後にコード例で解説いたします。
※ 2014/05/23 追記:ServiceWorkerの処理フローを示す図を追加いたしました。(本図は、Expertsの川田さんから提供いただきました)
Register(登録)
まずServiceWorkerとして動作させたい処理を登録(register)します。
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/assets/myapp.js').then( function(serviceWorker) { console.log("myapp.jsのインストールに成功しました") }, function(why) { console.log("myqpp.jsのインストールに失敗しました"); } ); }
上記により、/assets/myapp.jsがブラウザにダウンロード&インストールされます。ひとたびmyapp.jsがインストールされると、次回以降のhttp://www.example.com/配下に存在するページにアクセス時に、myapp.jsの処理が起動します。前述したように、ネットワークが切断状態であってもmyapp.jsは起動しますし、もちろんブラウザ自体が持つキャッシュ機構よりも先に動作します。
なお、上記のコード例では http://www.example.com/ の配下すべてのリソースが対象となりますが、/hoge/* のように、scopeを制限することも可能です。その場合、以下のとおり、オプション引数であるscopeを設定してください。
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/assets/myapp.js', { scope: 'hoge/*' // スコープを設定している }).then( ... ); }
続いて、ServiceWorkerの処理を記載するmyapp.jsの例を見ていきましょう。
Fetch(リソースの取得)
myapp.jsは、WebWorker同様にWorkerスレッドで動作します。そのため、DOM操作はServiceWorkerでは利用できません。ServiceWorkerは、fetchイベントを利用してリソースへのリクエスト・レスポンスを処理します。
this.addEventListener('fetch', function(event) { console.log(event.request); });
上記の例では、http://www.example.com/*へアクセスしたすべてのタイミングで、fetchイベントが起動し、event.requestオブジェクトがコンソールに表示されます。event.requestオブジェクトは、例えば、URL、メソッド、ヘッダ等を含んでいます。
上記は単にリクエストを処理しているだけですが、任意のレスポンスをブラウザへ返すこともできます。
this.addEventListener('fetch', function(event) { event.respondWith( new Response({ body: 'この内容はServiceWorkerが提供しています!' }) ); });
この例では、http://www.example.com/* へアクセスすると、「この内容はServiceWorkerが提供しています!」と表示されます。
リソースのCache
オフラインでも動作させるためには、キャッシュを利用する必要があります。キャッシュするためのコードは以下のようになります:
this.addEventListener('install', function(event) { var coreCache = new Cache();event.waitUntil(Promise.all([ caches.add('core-v1', coreCache), coreCache.add( '/app.html', '/fallback.html', '/assets/v1/base.css', '/assets/v1/video.webm' ) ])); });
ServiceWorkerのインストール成功時に発生するinstallイベントを利用して、キャッシュを準備します。Cacheおよびcaches(cacheListのインスタンス)は、ServiceWorkerが用意するストレージAPIです。上記例では、coreCache.add()にて、4つのリソースをキャッシュしています。
Cacheしたリソースを取得する
Cacheしたリソースを取得するには、fetchイベントを利用します:
this.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request).catch(function() { return cache.match('/fallback.html'); }) ); });
上記では、event.requestで指定される内容がすでにcachesにあれば(caches.matchでrejectされなければ)、cacheされている内容を応答します。cachesにない場合は、.catch(…)の処理に進み、fallback.htmlの内容がブラウザに返されます。
ServiceWorkerの更新
ServiceWorkerは、Webページにアクセスするたびに、更新があるかどうか確認します。HTTP Cache-Controlヘッダにて、確認頻度を減らすことも可能ですが、ApplicationCacheに存在する問題を解決するために、長くとも24時間ごとにブラウザが更新を確認します。
もし、ServiceWorkerが以下のコード例のように少しでも異なるならば、新しいバージョンであるとブラウザは判断し、ServiceWorkerをインストールします。
this.addEventListener('install', function(event) { var coreCache = new Cache();event.waitUntil(Promise.all([ caches.add('core-v2', coreCache), // v2にしている coreCache.add( '/app.html', '/fallback.html', '/assets/v2/base.css', // v2にしている '/assets/v1/video.webm' ) ])); });
インストール処理の間は、現段階のバージョンがfetchイベントに対応し、新しいバージョンのServiceWorkerのインストール動作はバックグラウンドで動作します。
インストールが完了したら、新しいバージョンのServiceWorkerは待ち状態になり、、すべてのページにて現段階のバージョンの利用されなくなるのを待ちます。現段階のバージョンが利用されなくなると、新しいバージョンが動作を開始します。このとき、activateイベントが発生します:
this.addEventListener('activate', function(event) { event.waitUntil( // 古いキャッシュの削除等を実施 ); });
activateイベントは、上記コード例のように古いキャッシュの削除等、新しいバージョンのServiceWorkerで不要となるリソースの掃除に利用されます。
まとめ
本記事では、ServiceWorkerの登場背景、利用イメージについて解説しました。ServiceWorkerは今後、オフラインWebアプリケーションのコア技術となっていくと想定されます。まだ、ブラウザの実装も進んでいないことからも分かるように、ServiceWorkerはWebの最先端です。
今後も、HTML5 Experts.jpでは、今後も読者の皆様にWeb最先端の情報をお届けいたします!