Service Worker

こんにちは、ATです。

運命のいたずらから Service Worker を触る機会があったので、その時経験した、あれやこれやを書いていきます。

前提:React TypeScript CRA

Service Worker とは

Worker

Service Worker の前にまず Worker の話を。
Worker とはなにかというと、バックグラウンドで実行されるタスクのことである。JavaScript では基本シングルスレッドで諸々の処理が行われる。そこにいわゆるマルチスレッドな処理を可能にする仕組みが Worker です。 Worker は別スレッドで動作しますが、マルチスレッドであることをあまり意識しないで使えるようになっています。とはいえそれは各種制約を課すことで実現されており、例えば、window オブジェクトのメソッドやプロパティに使えないものがあったり、DOMを直接操作することもできません。

Worker はインタフェースなので実態となるオブジェクトが数種類存在します。

・Dedicated Worker
・Shared Worker
・Service Worker

それぞれ、別スレッドで処理を実行するといったWorkerとしての基本は共通してますが、その他できることが違っています。全ての説明は割愛して今回は、Service Worker のみ紹介します。

Service Worker

Service Worker とは、基本的にウェブアプリケーション、ブラウザー、ネットワークの間に介在するプロキシサーバーのように振る舞います。サーバとの通信をキャッシュできPWAを実現するのに使用されてたりします。

Service Worker の他の Worker に対する利点として

・アプリのウィンドウがなくても処理ができる
・プッシュ通知の対応
・オリジンの違うwindowとやりとりできる
と言ったものがあります。

また、下記の特徴があります。

・DOM 操作ができない
 postMessage()でのメインスレッドにメッセージを送信しメインスレッド側で DOM 操作させることで一応 DOMに対して処理を行うことは可能らしいです。

・localStorageが使えない
 データの保持には IndexedDB を使う

・https か localhost 上でしか動作しない
 アプリの作りによってはデバッグで苦労する

Service Worker 使ってみた

*ここからの話はCRAを使ってるのが前提となります

有効化

index.tsx の

serviceWorker.unregister();

serviceWorker.register();

に変更する。
これだけで Service Worker が起動しキャッシュを始めます。

CRA(create-react-app) カスタマイズ

Service Worker のキャッシュをコントロールしたかったり、Service Worker に独自の処理を行わせるにはカスタマイズをほどこしてやらなければなりません。
これをおこなうのに react-app-rewired を使用します。 react-app-rewired はWebpackの3,4系までの対応なので使用の際には注意が必要です。

・インストール

yarn install react-app-rewired

・Config-overrides.js をルートディレクトリに作成

const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
module.exports = function override(config, env) {
  config.plugins = config.plugins.map(plugin => {
    if(plugin.constructor.name === 'GenerateSW') {
      return new WorkboxWebpackPlugin.InjectManifest({
        swSrc: './src/sw.js',
        swDest: 'service-worker.js'
      })
    }

    return plugin
  })

  return config
}

sw.jsを用意する。sw.js にコードを書くことでカスタマイズを行う。

・packeage.json 書き換え

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-scripts eject"
}

以上。 こうすることで、CRAでの設定を上書きし、独自の処理でカスタマイズできる。

TypeScript

Service Worker でTypeScript コードを実行する方法をいろいろ調べました。かなり苦しいが一応やれそうです。、やり方としては、別途トランスパイルしてjsファイルを作成しそれをimportして使います。正直微妙だが、他にやり方みつからず、、、ここで断念。

終わり

ここまで書いといてなんですが結局 Service Worker は使用しなかった。これは他にもいろいろ問題があったためで、必ずしも Service Worker だけに起因するモノではなかったです。今回のアプリのパスが特殊だったりとか、TypeScriptコードの使い回しがかなり困難だったり等ありまして、何より一番大きいのはDOM操作が直接できないということで、やりたい内容によってはかなり対応にコストがかかると感じました(あくまで今回のアプリでです)。postMessage()でメインスレッドとはやり取りできるので、DOM操作をメッセージで行うことはできるのですが、冗長だしそれに付帯する別の問題もあるしでやはり使いにくいとの結論になりました。ただ仕組みとしては色々面白いことができそうで、機会があれば使用してみたいと思ってます。