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

# はじめに
- Service Workerって聞いたことあります？

---

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

---

## どんな時に使える？
Cache APIやPush API、Fetch APIと一緒に使うとこで下記のようなことが可能になる

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

---

## 利用事例（1）
- [Webでプッシュ通知するサービスを個人開発で作ってみた＋ServiceWorkerPushAPIの実装方法まとめ](https://qiita.com/zaru/items/f6e821052abb1b18bb0b)
    - Service Worker + Push APIでプッシュ通知のSaasを作った話
    - 実装についても説明してある

---

## 利用事例（2） 
- [Mercari Web版 に Workbox で Service Worker を導入する話](http://tech.mercari.com/entry/2017/12/19/workbox)
    - Workbox（Googleのライブラリ。Cache APIを使いやすく）でオフライン対応

---

## 利用事例（3）
- [なぜ dev.to がこんなにも速く、こんなにも自分にとって感動的なのか](https://dev.to/mizchi/-devto--b5)
    - Service Workerのonfetchで割り込んでhtmlキャッシュを返す
    - オンマウスで記事を読み込む

---

## 利用事例（4） 
- [日経電子版 サイト高速化とPWA対応](https://speakerdeck.com/sisidovski/nikkei-high-performance-pwa)
    - 旧サイトと比較して約2倍の表示速度
    - スマホサイトのみ実装
    - 最初にスクロールし始めた時点で記事データを
読み込む

---

## どこでも使えるの？
- 主要ブラウザではほぼサポートされている（https://caniuse.com/#feat=serviceworkers）
<img width="1343" alt="スクリーンショット 2018-05-09 12.21.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/2469/58ecc1cd-3086-8b8f-e321-48cb05e6e2f0.png">

---

# 技術紹介

---

## Service Workerのライフサイクル
<img width="500" src="https://developers.google.com/web/fundamentals/primers/service-workers/images/sw-lifecycle.png?hl=ja" />

---

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

---

## 1. 登録
```js
navigator.serviceWorker.register('/sw.js').then(function(registration) {
    console.info('registration', registration);
})
```

---

## 2. インストール（oninstall）
```js
self.addEventListener('install', function (e) {
    console.info('install', e);
});
```

---

## 2. アクティベート（onactivate）
```js
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のゲストモード
- 最終的なレポジトリ
    - [github.com/kei4eva4/otona_sw](https://github.com/kei4eva4/otona_sw)

---

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

- プロジェクト作成　

```sh:bash
$ mkdir otona_sw
$ cd otona_sw
```

- htmlファイルを作成

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>さーびすわーかー</title>
</head>
<body>
さーびすわーかー
</body>
</html>
```

- package.jsonを作成

```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](https://qiita-image-store.s3.amazonaws.com/0/2469/0ce330a9-df4f-a795-19fe-2428d50c7b07.png)

---

## Service Workerの登録

```js: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);
});

```

```html: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](https://qiita-image-store.s3.amazonaws.com/0/2469/fb69842b-b6d5-05db-984d-09e27731dd6f.png)

### 2回目
![スクリーンショット 2018-05-09 18.14.49.png](https://qiita-image-store.s3.amazonaws.com/0/2469/bda746c5-9944-d32c-6208-b3a003a67f35.png)

---

### Service workers
![スクリーンショット 2018-05-09 18.16.12.png](https://qiita-image-store.s3.amazonaws.com/0/2469/cd8054cc-8de8-d44d-9b9a-b4cfd0171204.png)

---

- デバッグコンソール
    - [chrome://serviceworker-internals](chrome://serviceworker-internals)

---

# ハンズオン②

- リクエストを横取り＆レスポンスを生成

---

```html:index.html
<!-- ...省略 -->
<body>
<!-- ...省略 -->
<a href="test">横取り！</a>
<!-- ...省略 -->
</body>
```

```js: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](https://qiita-image-store.s3.amazonaws.com/0/2469/44719d07-ffe1-0599-97f6-ef29defb90f5.png)

---

# ハンズオン③

- 画像をキャッシュ

---

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

---

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

```html: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>
```


```js: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](https://qiita-image-store.s3.amazonaws.com/0/2469/80073d74-2cb6-61f3-0deb-30f8b23d5b51.png)

---

## 参考
- [そろそろはじめる Service Worker ＠ SpeeeKaigi #3 - Speee DEVELOPER BLOG](http://tech.speee.jp/entry/2017/12/21/111404)
- [Service Workerの基本とそれを使ってできること - Qiita](https://qiita.com/y_fujieda/items/f9e765ac9d89ba241154)

- [ServiceWorkerとCache APIを使ってオフラインでも動くWebアプリを作る - Qiita](https://qiita.com/horo/items/175c8fd7513138308930)

- [Service Worker周りの知識をアップデートする（2017年クリスマス版） - Qiita](https://qiita.com/otolab/items/72ac10a0880ea411d77e)

- [Service Worker registration & lifecycle // Speaker Deck](https://speakerdeck.com/jxck/service-worker-registration-and-lifecycle)

- [Service Worker の紹介  |  Web  |  Google Developers](https://developers.google.com/web/fundamentals/primers/service-workers/?hl=ja)

- [jakearchibald/simple-serviceworker-tutorial: A really simple ServiceWorker example, designed to be an interactive introduction to ServiceWorker](https://github.com/jakearchibald/simple-serviceworker-tutorial)