JSのServiceWorkerでWebページをオフラインで表示する

JavaScriptのServiceWorkerを利用してキャッシュさせてWebページをオフラインで表示させます。

一度表示させたページをキャッシュすることで、オフラインでもWebページが表示できます。

ServiceWorker

ServiceWorker(サービスワーカー)とはブラウザとサーバの通信の間に入っていろいろ制御できます。

それによりオフライン時にキャッシュしておいた内容を表示することができます。

通信を改ざんできるリスクがあるので、httpsかlocalhostでしか動作しません。

ちょっと小難しくて今まで避けてきたんですが、PWAと合わせてオフラインアプリを作りたいので調べました。

今回はキャッシュ機能だけの調査で、push通知などはありません。

サンプルコード

今回実験に使用したサンプルコードです。

ファイル構成

service_worker
├ media
│ ├ img1.png
│ ├ img2.png
│ └ style.css
├ index.html
├ service_worker.js
└ sw.js

上記のフォルダ構成をapacheのhtdocsの下に配置。

http://localhost/service_worker/でアクセスできるようにしています。

画像はペイントで適当に用意しました。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>service worker test</title>
  <link rel="stylesheet" type="text/css" href="media/style.css">
  <script src="./service_worker.js"></script>
</head>
<body>
  <h1>TEST</h1>
  <p>
    <img src="media/img1.png" width="300" height="200" />
  </p>
  <p>
    <img src="media/img2.png" width="300" height="200" />
  </p>
</body>
</html>

style.css

h1 {
  font-size: 1rem;
}

img {
  max-width: 100%;
  height: auto;
}

service_worker.js

const registerServiceWorker = async () => {
  if ('serviceWorker' in navigator) {
    try {
      await navigator.serviceWorker.register("/service_worker/sw.js", {
        scope: "/service_worker/",
      });
    } catch(e) {
      console.error(e);
    }
  }
}

registerServiceWorker();

sw.js

// キャッシュに登録する
const addResourcesToCache = async (resources) => {
  const cache = await caches.open("v1");
  await cache.addAll(resources);
};

self.addEventListener('install', event => {
  event.waitUntil(
    addResourcesToCache([
      "/service_worker/",
      "/service_worker/media/style.css",
      "/service_worker/media/img1.png",
    ]),
  );
});

// キャッシュがあれば返す、無ければネット
const cacheFirst = async (request) => {
  const cache = await caches.open("v1");
  const responseFromCache = await cache.match(request);
  if (responseFromCache) {
    return responseFromCache;
  }

  return fetch(request);
};

self.addEventListener('fetch', event => {
  if (event.request.method !== "GET") return;

  event.respondWith(cacheFirst(event.request));
});

キャッシュの確認

今回は下記の3ファイルをキャッシュするように設定しています。

まずは「http://localhost/service_worker/」にアクセスしてページを表示させます。

初回表示なのでサーバにリクエストを投げてダウンロードされます。

Sizeの項目に各ファイルのサイズが表示されています。

指定したファイルがキャッシュされているか確認します。

Chromeの開発者ツールを開いて「Application」タブに移動します。

StorageのCache storageに移動してキャッシュの内容を確認します。

キャッシュするように指定した3ファイルがキャッシュされていることがわかります。

これでブラウザにキャッシュされていることが確認できました。

オフラインにする

実験のためにオフラインにします。

apacheサーバを停止させてもいいですが、Chromeの開発ツールでOfflineにできます。

ApplicationのService workersを選んで、Offlineにチェックを入れます。

これでオフラインにできます。

ローカルにapacheを立てている場合は、LANケーブル抜いてもダメですので注意してください。

オフラインで確認

オフライン時にキャッシュされている内容が表示されるかを確認します。

ただ、ブラウザのリロードボタンを押すだけです。

memoryやdisk cacheを無効にするために「Disable cache」にチェックを入れるとテストが楽です。

スーパーリロードでもいいです。

Offlineでもキャッシュされていたファイルは表示されています。

img1.pngはキャッシュ指定したので表示されています。

img2.pngはキャッシュ指定していないので、表示されません。

Apache(webサーバ)を止めても同じ結果になります。

まとめ

Service workersを利用してブラウザにキャッシュさせれば、オフラインでもWebページが表示できます。

ReactなどのSPAはbuildしたjsをキャッシュさせれば、オフラインでも動くはずです。

PWAにすればオフラインでもネイティブアプリのようなアプリが実現できると思います。

データ保存もローカルストレージを利用すれば永続化できるはずです。

WebAssembly版のSQLiteを利用すれば、ローカルでリレーショナルDBを利用できるので

スケジュール管理やメモアプリなどが実現できると思います。

個人的な目標は、オフラインで動作可能なタスク管理アプリを作成することです。

個人のタスクなんてスマホ内にあれば十分ですから。

PWA + Service Worker + SQLiteで実現することを目指しています。

関連記事
最新記事