Service Worker Primer
Hiroki Nakagawa
(nhiroki@chromium.org)
2015/04/04
自己紹介
Hiroki Nakagawa (@nhiroki_)
Chrome ソフトウェアエンジニア
Storage や Worker 周りを担当
File, FileSystem, Quota, SyncFS, ServiceWorker
質問
Service Worker って
なんだろう?
Service Worker ってなんだろう?
“Better AppCache?”
“ブラウザ内 HTTP プロキシ?”
“Native App と Web のギャップを埋める?”
Service Worker ってなんだろう?
“Better AppCache?”
“ブラウザ内 HTTP プロキシ?”
“Native App と Web のギャップを埋める?”
どれも Service Worker の可能性の一つに過ぎない
それじゃ Service Worker って何?
ページのバックグラウンドで動く
イベント駆動の JavaScript 環境
(ゆえに “Service” Worker)
対応イベントを増やすことで様々なサービスを提供できる
Service Worker の
ある世界
Service Worker
Page
example
.com
登録
コントロール
HTTP
リクエスト
“ブラウザ内 HTTP プロキシ”
Service Worker
Page
example
.com
プッシュ通知
バックグラウンド同期
“Native App と Web のギャップを埋める”
Service Worker
Page
example
.com
HTTP リクエスト
キャッシュ済み
レスポンス
“Better AppCache”
3 STEP で SW の基本を理解しよう
オフライン対応したページを作ってみます
注意事項
STEP 1
Service Worker の登録
Service Worker の登録
// index.html
navigator.serviceWorker
.register(‘/sw.js’, {scope: ’/’})
.then(function(registration) {
// Success!
})
.catch(function(error) {
// Error...
});
// sw.js
self.oninstall = function(event) {
// 新しい SW の登録中に呼ばれる。
// リソースをキャッシュしたりする。
};
self.onactivate = function(event) {
// SW がアクティブになる直前に呼ば
// れる。古いリソースを消したりする。
};
SW のライフサイクル
oninstall
onactivate
register()
イベント待ち
次のステップでは
イベントハンドラを定義して、
サービスを提供してみます
DevTools で SW の状態を確認可能
Activate された!
STEP 2
onfetch イベントによる
リクエストのフック
activate 後に利用可能なイベント
onfetch によるリクエストの横取り
スコープ内のページが HTTP リクエストを発行したときに発火
横取り可能なリクエスト
ページロード, <a>, <img>, XHR(), fetch()...
onfetch でどんなことができる?
Page
example
.com
Service Worker
例えば、リクエストのロギング
CacheStorage
例えば、カスタムレスポンスや
キャッシュ済みレスポンスを返す
onfetch
STEP2 ではカスタムレスポンスを
返すようにしてみます
ネットワークリクエストの横取り
// sw.js
self.onfetch = function(fetchEvent) {
var requestURL = new URL(fetchEvent.request.url);
if (requestURL.pathname == ‘/notfound.html’) {
// 存在しないページ “/notfound.html” へのアクセスなら
// 独自のレスポンスを生成して返す
fetchEvent.respondWith(new Response(‘Goodbye 404!’));
return;
}
// 何もしないとネットワークリクエストにフォールバック
};
/notfound.html を開いてみると...
STEP 3
CacheStorage API による
オフライン対応
(再掲)リクエストの横取り
// sw.js
self.onfetch = function(fetchEvent) {
var requestURL = new URL(fetchEvent.request.url);
if (requestURL.pathname == ‘/notfound.html’) {
// 存在しないページ “/notfound.html” へのアクセスなら
// 独自のレスポンスを生成して返す
fetchEvent.respondWith(new Response(‘Goodbye 404!’));
return;
}
// 何もしないとネットワークリクエストにフォールバック
};
(再掲)リクエストの横取り
// sw.js
self.onfetch = function(fetchEvent) {
var requestURL = new URL(fetchEvent.request.url);
if (requestURL.pathname == ‘/notfound.html’) {
// 存在しないページ “/notfound.html” へのアクセスなら
// 独自のレスポンスを生成して返す
fetchEvent.respondWith(new Response(‘Goodbye 404!’));
return;
}
// 何もしないとネットワークリクエストにフォールバック
};
キャッシュ済みレスポンスを
返すように書き換えれば
オフライン対応完了!
CacheStorage API
リソースをキャッシュしてみる
// 未実装の API があるため polyfill を使う
// https://github.com/coonsta/cache-polyfill
importScripts(‘serviceworker-cache-polyfill.js’);
self.oninstall = function(installEvent) {
var promise = self.caches.open(‘v1’)
.then(function(cache) {
return cache.add(‘/dog.png’);
});
installEvent.waitUntil(promise);
};
|
|
|
req1 | res1 |
req2 | res2 |
... | ... |
req1 | res1 |
req2 | res2 |
... | ... |
‘v1’
キャッシュの構造
‘static-assets’
self.caches
(CacheStorage)
Cache
キャッシュしたリソースをDevTools で確認してみる
キャッシュしたリソースを使ってみる
self.onfetch = function(fetchEvent) {
var requestURL = new URL(event.request.url);
if (requestURL.pathname == '/notfound.html') {
var res = new Response(‘<h1>Goodbye 404!</h1><img src='/cat.png'>’);
res.headers.set(‘content-type’, ‘text/html’);
event.respondWith(res);
} else if (requestURL.pathname.indexOf(‘png’) != -1) {
// png 画像を全て犬の画像に置き換える
var promise = self.caches.match(‘/dog.png’)
.then(function(cachedResponse) { return cachedResponse; })
.catch(function(error) { return fetch(request); // Network fallback });
fetchEvent.respondWith(promise);
}
};
/notfound.html を開いてみると...
CacheStorage API と fetch() の活用例
環境に応じて様々な UX を提供できる
http://jakearchibald.com/2014/offline-cookbook/
http://jakearchibald.com/2014/offline-cookbook/
SW をもっと理解する
ページのコントロールタイミング
SW がアクティブになった後に開いたページがコントロールされる
アクティブになる前に開いたページをコントロール下に置く claim() という API もあります
Service Worker の更新
コントロールされているページをロードすると自動的に SW スクリプトの更新が確認される
コントロールされているページがいる限り新しい SW に置き換わらない (開発中のハマりポイント)
キャッシュの更新
self.oninstall = function(installEvent) {
var promise = self.caches.open(‘v2’)
.then(function(cache) {
return cache.add(‘/bird.png’); });
installEvent.waitUntil(promise);
};
self.activate = function(activateEvent) {
activateEvent.waitUntil(self.caches.delete(‘v1’));
};
Service Worker のスコープ
navigator.serviceWorker.register(
‘/foo/sw.js’, {scope: ‘/foo/bar/’})
SW がコントロールするページの範囲
スクリプトパス以下のみ指定可能 (ハマりポイントその 2)
HTTP ヘッダによってパス制限の解除も可能
“Service-Worker-Allowed: ‘/allowed-path’”
Service Worker の無効化
SW スクリプトのリクエストには “Service-Worker: script” ヘッダがつく
サーバ側で SW を無効化できる
利用例
Trained To Thrill (SW Demo)
https://jakearchibald.github.io/trained-to-thrill/
sw-precache
まとめ
おわり
資料
(参考)登録処理関連の API
// 登録情報の取得
navigator.serviceWorker.getRegistration()
.then(function(registration) {
console.log(registration.scope) });
// 登録の解除
registration.unregister().then(function(success) { ... });
(参考)Request, Response
(参考)CacheStorage API