1 of 48

Service Worker Primer

Hiroki Nakagawa

(nhiroki@chromium.org)

2015/04/04

2 of 48

自己紹介

Hiroki Nakagawa (@nhiroki_)

Chrome ソフトウェアエンジニア

Storage や Worker 周りを担当

File, FileSystem, Quota, SyncFS, ServiceWorker

3 of 48

質問

Service Worker って

なんだろう?

4 of 48

Service Worker ってなんだろう?

“Better AppCache?”

“ブラウザ内 HTTP プロキシ?”

“Native App と Web のギャップを埋める?”

5 of 48

Service Worker ってなんだろう?

“Better AppCache?”

“ブラウザ内 HTTP プロキシ?”

“Native App と Web のギャップを埋める?”

どれも Service Worker の可能性の一つに過ぎない

6 of 48

それじゃ Service Worker って何?

ページのバックグラウンドで動く

イベント駆動の JavaScript 環境

(ゆえに “Service” Worker)

対応イベントを増やすことで様々なサービスを提供できる

7 of 48

Service Worker の

ある世界

8 of 48

  • SW はページ URL の範囲に対して登録される
    • 範囲内のページに対してサービスを提供 (コントロール)
    • コントロールしているページからのリクエストを横取りするイベント

Service Worker

Page

example

.com

登録

コントロール

HTTP

リクエスト

“ブラウザ内 HTTP プロキシ”

9 of 48

  • ページを開いていない状態でもイベントに応じて起動
  • プッシュ通知やバックグラウンド同期イベントを処理

Service Worker

Page

example

.com

プッシュ通知

バックグラウンド同期

“Native App と Web のギャップを埋める”

10 of 48

  • 一度インストールすればオフラインでも動作
    • キャッシュ済みレスポンスを返せばオフラインアプリとなる
    • オフライン時の挙動をスクリプトできめ細やかに制御

Service Worker

Page

example

.com

HTTP リクエスト

キャッシュ済み

レスポンス

“Better AppCache”

11 of 48

3 STEP で SW の基本を理解しよう

オフライン対応したページを作ってみます

  1. Service Worker の登録
  2. リクエストのフック
  3. オフライン対応

12 of 48

注意事項

  • Chrome Dev もしくは Canary を使用することをオススメ
  • Service Worker は https または localhost 上でのみ動作します

13 of 48

STEP 1

Service Worker の登録

14 of 48

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 がアクティブになる直前に呼ば

// れる。古いリソースを消したりする。

};

15 of 48

SW のライフサイクル

oninstall

onactivate

register()

イベント待ち

次のステップでは

イベントハンドラを定義して、

サービスを提供してみます

16 of 48

DevTools で SW の状態を確認可能

Activate された!

17 of 48

STEP 2

onfetch イベントによる

リクエストのフック

18 of 48

activate 後に利用可能なイベント

  • onfetch
  • onpush (Push API)
  • onnotificationclick (Notifications API)
  • onsync (BackgroundSync)
  • onconnect (navigator.connect)
  • ...

19 of 48

onfetch によるリクエストの横取り

スコープ内のページが HTTP リクエストを発行したときに発火

横取り可能なリクエスト

ページロード, <a>, <img>, XHR(), fetch()...

20 of 48

onfetch でどんなことができる?

Page

example

.com

Service Worker

例えば、リクエストのロギング

CacheStorage

例えば、カスタムレスポンスや

キャッシュ済みレスポンスを返す

onfetch

STEP2 ではカスタムレスポンスを

返すようにしてみます

21 of 48

ネットワークリクエストの横取り

// 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;

}

// 何もしないとネットワークリクエストにフォールバック

};

22 of 48

/notfound.html を開いてみると...

23 of 48

STEP 3

CacheStorage API による

オフライン対応

24 of 48

(再掲)リクエストの横取り

// 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;

}

// 何もしないとネットワークリクエストにフォールバック

};

25 of 48

(再掲)リクエストの横取り

// 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;

}

// 何もしないとネットワークリクエストにフォールバック

};

キャッシュ済みレスポンスを

返すように書き換えれば

オフライン対応完了!

26 of 48

CacheStorage API

  • Key-Value ストレージ
    • Key: Request オブジェクト
    • Value: Response オブジェクト
  • ブラウザの HTTP キャッシュとは独立
  • オリジン毎に管理
  • 現在は SW 上でのみ使えるが、まもなくページ上でも使用可能に

27 of 48

リソースをキャッシュしてみる

// 未実装の 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

28 of 48

キャッシュしたリソースをDevTools で確認してみる

29 of 48

キャッシュしたリソースを使ってみる

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);

}

};

30 of 48

/notfound.html を開いてみると...

31 of 48

CacheStorage API と fetch() の活用例

  • キャッシュ済みデータだけ返し、後から最新のデータを表示する (チャットなど)
  • テンプレートだけキャッシュし、コンテンツは毎回取得する (ニュースアプリなど)
  • デバイスに応じてリソースを切り替える

環境に応じて様々な UX を提供できる

32 of 48

http://jakearchibald.com/2014/offline-cookbook/

33 of 48

http://jakearchibald.com/2014/offline-cookbook/

34 of 48

SW をもっと理解する

35 of 48

ページのコントロールタイミング

SW がアクティブになった後に開いたページがコントロールされる

アクティブになる前に開いたページをコントロール下に置く claim() という API もあります

参考:ServiceWorker のスコープとページコントロールについて

36 of 48

Service Worker の更新

コントロールされているページをロードすると自動的に SW スクリプトの更新が確認される

  • Cache-control ヘッダに従う
  • 24 時間経ったら必ずチェックを行う

コントロールされているページがいる限り新しい SW に置き換わらない (開発中のハマりポイント)

  • ページを開き直す or DevTools から unregister
  • skipWaiting() で強制的に置き換えることもできる

37 of 48

キャッシュの更新

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’));

};

38 of 48

Service Worker のスコープ

navigator.serviceWorker.register(

‘/foo/sw.js’, {scope: ‘/foo/bar/’})

SW がコントロールするページの範囲

スクリプトパス以下のみ指定可能 (ハマりポイントその 2)

HTTP ヘッダによってパス制限の解除も可能

“Service-Worker-Allowed: ‘/allowed-path’”

参考:ServiceWorker のスコープとページコントロールについて

39 of 48

Service Worker の無効化

SW スクリプトのリクエストには “Service-Worker: script” ヘッダがつく

サーバ側で SW を無効化できる

40 of 48

利用例

41 of 48

Trained To Thrill (SW Demo)

https://jakearchibald.github.io/trained-to-thrill/

  • Flickr の API で取得した画像をキャッシュ
  • オフライン対応
  • ソースコードを公開中

42 of 48

sw-precache

  • grunt/gulp プラグイン
  • キャッシュしたいリソースを宣言的に記述
  • キャッシュに更新があったら自動的に取得

43 of 48

まとめ

  • SW はページのバックグラウンドで動くイベント駆動の JS 実行環境
  • イベントハンドラを登録することで
    • Better AppCache (onfetch + CacheStorageAPI)
    • ブラウザ内 HTTP プロキシ (onfetch)
    • Native App や Web のギャップを埋める (Push API, Web Notifications, etc)

44 of 48

おわり

45 of 48

資料

46 of 48

(参考)登録処理関連の API

// 登録情報の取得

navigator.serviceWorker.getRegistration()

.then(function(registration) {

console.log(registration.scope) });

// 登録の解除

registration.unregister().then(function(success) { ... });

SPEC: navigator.serviceWorker

SPEC: registration

47 of 48

(参考)Request, Response

48 of 48

(参考)CacheStorage API