入門

Service Worker入門〜導入方法から事例紹介まで〜@オトナのプログラミング勉強会

はじめに

  • Service Workerって聞いたことあります?

Service Workerとは

  • Webページとは別にバックグランドで実行するスクリプト
    • DOMに直接アクセスできない
    • ブラウザを開いていなくても動作可能
    • プログラム可能なネットワークプロキシとして動作可能
    • HTTPS必須(またはlocalhost)

どんな時に使える?

Cache APIやPush API、Fetch APIと一緒に使うとこで下記のようなことが可能になる

  • キャッシュ・アセットの制御
  • プッシュ通知
  • オフライン対応
  • PWA対応

利用事例(1)


利用事例(2)


利用事例(3)


利用事例(4)


どこでも使えるの?


技術紹介


Service Workerのライフサイクル


Service Workerのライフサイクル(1)

  • 1. 登録
    • jsファイルを指定
  • 2. インストール(oninstall)
    • 静的なアセットを読み込んだりする
  • 3. アクティベート(onactivate)
    • このステップが終わるとすコプ内の全てのページのコントロールが完了
    • 初回はコントロールされず、次に読み込まれた際にコントロールされる

1. 登録

navigator.serviceWorker.register('/sw.js').then(function(registration) {
    console.info('registration', registration);
})

2. インストール(oninstall)

self.addEventListener('install', function (e) {
    console.info('install', e);
});

2. アクティベート(onactivate)

self.addEventListener('activate', function (e) {
    console.info('activate', e);
});

Service Workerのライフサイクル(2)

  • ブラウザスレッド⇔Service Worker間のイベント
    • フェッチ(onfetch)
      • クライアントからサーバーアクセスの際に呼ばれる
    • メッセージ(onmessage)
      • ブラウザスレッドのメソッドからのメッセージング

Service Workerのライフサイクル(3)

  • ブラウザーネットワーク⇔Service Worker間のイベント
    • シンク(onsync)
      • オンライン時に呼ばれる
    • プッシュ(onpush)
      • プッシュ通知を受け取った時に呼ばれる

ハンズオン①

  • Servie Workerを登録&起動の確認

事前準備

  • npmがインストールされている(または、何かしらの言語でサーバーがたてられればOK)
  • 開発はChromeのゲストモード
  • 最終的なレポジトリ

プロジェクトの作成&インストール等

  • プロジェクト作成 
bash
$ mkdir otona_sw
$ cd otona_sw
  • htmlファイルを作成
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>さーびすわーかー</title>
</head>
<body>
さーびすわーかー
</body>
</html>
  • package.jsonを作成
package.json
{
  "scripts": {
    "start": "http-server -c-1"
  },
  "devDependencies": {},
  "dependencies": {
    "http-server": "^0.11.1"
  }
}
  • パッケージのインストール&サーバーの起動
$ npm install
$ npm start

スクリーンショット 2018-05-09 18.13.20.png


Service Workerの登録

sw.js
// インストール
self.addEventListener('install', function (e) {
    console.info('install', e);
});

// アクティベート
self.addEventListener('activate', function (e) {
    console.info('activate', e);
});

// フェッチ
self.addEventListener('fetch', function (e) {
    console.info('fetch', e);
});

index.html
<script>
    // Service Worker API が存在しているかをチェック
    if ('serviceWorker' in navigator) {
        console.log('service worker is active');
        navigator.serviceWorker.register('/sw.js').then(function (registration) {
            // 登録成功
            console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }).catch(function (err) {
            // 登録失敗 :(
            console.log('ServiceWorker registration failed: ', err);
        });
    }
</script>

1回目

スクリーンショット 2018-05-09 18.14.44.png

2回目

スクリーンショット 2018-05-09 18.14.49.png


Service workers

スクリーンショット 2018-05-09 18.16.12.png



ハンズオン②

  • リクエストを横取り&レスポンスを生成

index.html
<!-- ...省略 -->
<body>
<!-- ...省略 -->
<a href="test">横取り!</a>
<!-- ...省略 -->
</body>
sw.js
// ...省略
// フェッチ
self.addEventListener('fetch', function (e) {
    console.info('fetch', e);

    if (e.request.url.indexOf('test') != -1) {
        e.respondWith(new Response('Hello world'));
    } 
});

実行結果

スクリーンショット 2018-05-09 19.30.30.png


ハンズオン③

  • 画像をキャッシュ

注意

  • 一度ゲストモードのブラウザを閉じてください

インストールイベントで画像をキャッシュ

index.html
<!-- ...省略 -->
<body>
<!-- ...省略 -->
<div id="content"></div>
<script>
    // ...省略
    const element = document.querySelector('#content');
    for(let i=1;i<=100;i++){
        const image = document.createElement('img')
        image.setAttribute('src', `https://robohash.org/${i}.png?size=100x100`)

        element.appendChild(image)
    }
    // ...省略
</script>
sw.js
let urlsToCache = [
    '/',
];

for(let i=1;i<=100;i++){
    urlsToCache.push(`https://robohash.org/${i}.png?size=100x100`)
}

// インストール
self.addEventListener('install', function (e) {
    console.info('install', e);
    e.waitUntil(
        caches.open('v1').then((cache) => {
            // 画像をキャッシュ対象に追加
            cache.addAll(urlsToCache)
        })
    )
});

// フェッチ
self.addEventListener('fetch', function (e) {
    console.info('fetch', e);
    e.respondWith(
        caches.match(e.request)
            .then(function (response) {
                    // キャッシュがあったのでそのレスポンスを返す
                    if (response) {
                        console.info(`Using cache: ${e.request.url}`);
                        return response;
                    }
                    return fetch(e.request);
                }
            )
    );

});

1回目と2回目を試す&オフラインで試す

スクリーンショット 2018-05-09 18.49.17.png


参考