はじめに
- Service Workerって聞いたことあります?
Service Workerとは
- Webページとは別にバックグランドで実行するスクリプト
- DOMに直接アクセスできない
- ブラウザを開いていなくても動作可能
- プログラム可能なネットワークプロキシとして動作可能
- HTTPS必須(またはlocalhost)
どんな時に使える?
Cache APIやPush API、Fetch APIと一緒に使うとこで下記のようなことが可能になる
- キャッシュ・アセットの制御
- プッシュ通知
- オフライン対応
- PWA対応
利用事例(1)
-
Webでプッシュ通知するサービスを個人開発で作ってみた+ServiceWorkerPushAPIの実装方法まとめ
- Service Worker + Push APIでプッシュ通知のSaasを作った話
- 実装についても説明してある
利用事例(2)
-
Mercari Web版 に Workbox で Service Worker を導入する話
- Workbox(Googleのライブラリ。Cache APIを使いやすく)でオフライン対応
利用事例(3)
-
なぜ dev.to がこんなにも速く、こんなにも自分にとって感動的なのか
- Service Workerのonfetchで割り込んでhtmlキャッシュを返す
- オンマウスで記事を読み込む
利用事例(4)
-
日経電子版 サイト高速化とPWA対応
- 旧サイトと比較して約2倍の表示速度
- スマホサイトのみ実装
- 最初にスクロールし始めた時点で記事データを
読み込む
どこでも使えるの?
- 主要ブラウザではほぼサポートされている(https://caniuse.com/#feat=serviceworkers)
技術紹介
Service Workerのライフサイクル
Service Workerのライフサイクル(1)
-
- 登録
- jsファイルを指定
-
- インストール(oninstall)
- 静的なアセットを読み込んだりする
-
- アクティベート(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)
- ブラウザスレッドのメソッドからのメッセージング
- フェッチ(onfetch)
Service Workerのライフサイクル(3)
- ブラウザーネットワーク⇔Service Worker間のイベント
- シンク(onsync)
- オンライン時に呼ばれる
- プッシュ(onpush)
- プッシュ通知を受け取った時に呼ばれる
- シンク(onsync)
ハンズオン①
- 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
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回目
2回目
Service workers
- デバッグコンソール
ハンズオン②
- リクエストを横取り&レスポンスを生成
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'));
}
});
実行結果
ハンズオン③
- 画像をキャッシュ
注意
- 一度ゲストモードのブラウザを閉じてください
インストールイベントで画像をキャッシュ
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);
}
)
);
});