やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

JSのServiceWorkerとCacheを使う

 オフラインでも動作するWebアプリのために。

成果物

情報源

ServiceWorker

 ServiceWorkerとは、プロキシサーバのように振る舞う。リソースへのリクエストを横取り(hook)して任意の処理を行う。ふつうはCache APIを用いてリソースをキャッシュ(ローカルに保存)する。これにてネットが使えないオフライン環境でも閲覧できる。オフライン時はキャッシュを使うことで。

実装

  1. ServiceWorkerのイベント処理を定義したjsファイルを登録する: navigator.serviceWorker.register()
  2. 1のファイルでイベント処理を定義する: イベントハンドラ: install,fetch,activate
イベント 概要 役割
install 初回に行われる。2回目以降は実行されない。 初回時すべてをキャッシュする。
fetch ファイル単位で処理できる キャッシュになければ追加する。
activate 有効化。 完了時の処理

コード例

ファイル 役割
index.html main.jsを呼び出す。
main.js ServiceWorkerTest.jsを呼び出す。
ServiceWorkerTest.js sw.jsを登録する。
sw.js ServiceWorkerのイベント処理を実装する。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>JSのServiceWorkerを使う。</title>
    <script src="./main.js" type="module"></script>
</head>
<body>
<h1>JSのServiceWorkerを使う。</h1>
<p id="kind"></p>
</body>
</html>

main.js

import ServiceWorkerTest from './ServiceWorkerTest.js';
window.addEventListener('load', (event) => {
    let test = new ServiceWorkerTest();
});

ServiceWorkerTest.js

export default class ServiceWorkerTest {
    constructor() {
        this.#setup();
    }
    #setup() {
        console.log('has navigator:',navigator);
        console.log(`has serviceWorker: ${'serviceWorker' in navigator}`);
        navigator.serviceWorker.register('sw.js', {
            scope: './'
        }).then(function(registration) {
            var serviceWorker;
            if (registration.installing) {
                console.log('installing');
                serviceWorker = registration.installing;
                document.querySelector('#kind').textContent = 'installing';
            } else if (registration.waiting) {
                console.log('waiting');
                serviceWorker = registration.waiting;
                document.querySelector('#kind').textContent = 'waiting';
            } else if (registration.active) {
                console.log('active');
                serviceWorker = registration.active;
                document.querySelector('#kind').textContent = 'active';
            }
            if (serviceWorker) {
                console.log(`state: ${serviceWorker.state}`);
                serviceWorker.addEventListener('statechange', function (e) {
                    console.log(`state: ${e.target.state}`);
                });
            }
        }).catch (function (error) {
            console.log(error);
        });
    }

sw.js

const VERSION = 'v1';

self.addEventListener('install', function(e) {
    console.log(`install event !!`, e);
    // キャッシュ完了まで待機する
    e.waitUntil(
        caches.open(VERSION).then((cache) => {
            return cache.addAll([
                './index.html',
                './main.js',
                './ServiceWorkerTest.js',
            ]);
        })
    );
});
self.addEventListener('fetch', function(e) {
    console.log('fetch event !!', e);
    e.respondWith(
        // リクエストに一致するデータがキャッシュにあるかどうか
        caches.match(e.request).then(function(cacheResponse) {
            console.log('has cache: ', cacheResponse);
            // キャッシュがあればそれを返す、なければリクエストを投げる
            return cacheResponse || fetch(e.request).then(function(response) {
                return caches.open(VERSION).then(function(cache) {
                    // レスポンスをクローンしてキャッシュに入れる
                    cache.put(e.request, response.clone());
                    // オリジナルのレスポンスはそのまま返す
                    return response;
                });  
            });
        })
    );
});
self.addEventListener('activate', function(e) {
    console.log(`activate event !!`, e);
});

テスト方法

 以下のいずれかで動作確認できる。

  • ローカルサーバ
  • HTTPSサーバ

ローカルサーバ

  1. ターミナルを起動する
    1. コードをダウンロードするgit clone https://github.com/ytyaru/JS.ServiceWorker.20210113144638
    2. cd JS.ServiceWorker.20210113144638/docs/3でカレントディレクトリを移動する
    3. ローカルサーバを起動するpython3 -m http.server 8000
  2. ブラウザを起動する
    1. シークレットモードで開く
    2. ブラウザのURL欄にlocalhost:8000を入力する(http://0.0.0.0:8000だとServiceWorkerが見つからずエラーになる)
    3. デベロッパツールを開く(chromeならCtrl+Shift+I)
    4. ログをみる(install event !!, activate event !!
    5. ページ更新する(F5キー押下)
    6. ログをみる(fetch event !!

 以上で、sw.js内の3つのイベントが実行されたことを確認できる。

参考

HTTPSサーバ

 ServiceWorkerHTTPSサーバでないと動作しないらしい。GitHub PagesならOK。テストというか本番。

所感

使いこなすの大変そう

 使いこなすの大変そう。キャッシュの処理をゴリゴリ書かねばならないのが大変。今回のサンプルは古くなったファイルを削除する処理がない。本番時はそういうのも書かないと。

ローカルファイルでは動かない

 HTMLファイルをローカルで開いても動かない。file://環境では動かない。

 私としては、HTMLファイルを保存しておいて動作して欲しかった。

 ローカルサーバなんて起動したくない! と思うでしょ? したらCORSエラーになる。

ローカルファイルだとCORSエラーになる

JSのimportが使えなくなる  JSのimport,export, HTMLの<script type="mode">が使えなくなる。

 ローカルサーバを起動せず、file://で開く。するとCORSエラーになる。import,exportを使いたくばCORSエラーを回避するしかない。そのための方法が、ローカルサーバである。今回はServiceWorkerもローカルサーバが必要。ならもうローカルサーバ使うしかないじゃんね。

 べつにimport,exportだけなら使わなくても書ける。ちょっと面倒だけど、htmlに<script src="A.js">,<script src="B.js">みたいに書けばいい。だが、先述のとおり、今回はServiceWorkerでもローカルサーバが必要。ならもうローカルサーバ使ってimportもしたほうがいい。

 こういう判断がむずかしい。アレとコレの仕様を把握しつつ、何ができるのか、どうやるのが目的に合っているか、考えなきゃいけない。JavaScriptは環境差によるコード方法、テスト方法が激ムズだから嫌い。

ローカルサーバで表示しないと動かない

 以下のようにローカルサーバを起動し、ブラウザでlocalhost:8000としないと閲覧できない。

python3 -m http.server 8000

http://0.0.0.0:8000/ではServiceWorkerオブジェクトが見つからない

 ローカルサーバを起動したまではいい。けどURLはlocalhost:8000でないとServiceWorkerオブジェクトが見つからない。仕様。そのため、ServiceWorkerの動作確認ができない。

 紛らわしいことに、python3 -m http.server 8000でローカルサーバを起動させたら、以下のようなURLを参照しろと出る。たしかにそれでHTMLは表示される。だが、ServiceWokerオブジェクトは見つからない。

Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

対象環境

$ uname -a
Linux raspberrypi 5.4.83-v7l+ #1379 SMP Mon Dec 14 13:11:54 GMT 2020 armv7l GNU/Linux