LoginSignup
22
15

More than 5 years have passed since last update.

create-react-appのregisterServiceWorkerで更新があったらnoticeを出す奴にトライする

Last updated at Posted at 2018-03-19

create-react-app には registerServiceWorker.js というservice workerを登録する仕組みが乗っかっている。

これはoffline cacheを実現するものなのだが、cache-first strategyという手法を取っており、コードを更新してデプロイした際の取り回しがなかなか手こずるので、割といつもdisableにしがち。

だが、いつまでもこのままというのもちょっと気持ち悪いので、一度service-workerの更新に合わせて「更新してください」みたいなモーダルを出すexampleをやってみた。


demo

ソース


仕組みおさらい

create-react-appは sw-precache-webpack-pluginというpluginを組み込んでserviceWorkerを組み込んでいる。

このpluginはwebpack上でマニフェスト的な値を生成し、それとsw-precacheを組わせて裏側でfetchする仕組みをもたせている。

// service-worker.js
 :
// ここらへんの値をwebpackから吐いている。
var precacheConfig = [["/index.html","08af9741a3d97d13dec114a974062844"],["/static/css/main.c17080f1.css","302476b8b379a677f648aa1e48918ebd"],["/static/js/main.0ccb3c41.js","426e3841d0fed136b6bb86108f30e5e2"],["/static/media/logo.5d5d9eef.svg","5d5d9eefa31e5e13a6610d9fa7a283bb"]];

// :
// あとはsw-preacacheの生成コード

そして、これを registerServiceWorker.jsが登録することでキャッシュが動いている。

// registerServiceWorker.js
  :
  :
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
      if (isLocalhost) {
        // This is running on localhost. Lets check if a service worker still exists or not.
        checkValidServiceWorker(swUrl);

1. 準備

create-react-appの標準状態だと、hot reloadがあったりしてlocalでのservice workerの検証がしづらい。

なのでejectして、加えてserveでローカルサーバーで実行する。 (serveはcache-controlを0にする必要もある)

{
  "scripts": {
    "start":
      "NODE_ENV=production webpack --config config/webpack.config.prod.js --watch",
    "static": "serve -c 0 -s build -o --local"
  }
}

これでこんな感じでstart。

$ yarn start & yarn static

2. onupdatefoundからイベントを発火

ServiceWorkerが更新を検知した際、registerServiceWorkerのonupdatefound が発火するので、ここにevent発火を仕掛ける。

registration.onupdatefound = () => {
  const installingWorker = registration.installing;
    installingWorker.onstatechange = () => {
        if (navigator.serviceWorker.controller) {
          // At this point, the old content will have been purged and
          // the fresh content will have been added to the cache.
          // It's the perfect time to display a "New content is
          // available; please refresh." message in your web app.
          console.log("New content is available; please refresh.");

          // イベント発火
          const event = new Event("newContentAvailable");
          window.dispatchEvent(event);
        } else {
           :

ちなみに、nextバージョン では、config.onUpdate というコールバックが追加されており、registerServiceWorker.jsに直接手を加えなくても大丈夫になりそう

2. <ReloadModal>を作る

次に <ReloadModal>を作る。これは発火されたグローバルな newContentAvailable イベントを拾ってくるためのもの。
この例では、わかりやすさ重視でstate使ったり、ライフサイクルをそのまま使っているので、必要に応じてreduxにしたりなにかしたりすると良さそう。


// Main App
class App extends Component {
  render() {
    return (
      <div className="App">
        <ReloadModal />
            :
      </div>
    );
  }
}

class ReloadModal extends Component {
  state = {
    show: false
  };
  componentDidMount() {
    // グローバルイベントを引っ掛ける。
    window.addEventListener("newContentAvailable", () => {
      this.setState({
        show: true
      });
    });
  }
  onClick = () => {
    // リロードする
    window.location.reload(window.location.href);
  };
  render() {
    if (!this.state.show) {
      return null;
    }
    // <Modal> は単なる固定モーダルのコンポーネントなので省略。
    return (
      <Modal onClick={this.onClick}>
        <span> New Content Available!please reload </span>
      </Modal>
    );
  }
}

まとめ

とりあえずこれで最初に出したデモみたいなものは出来た。
今回のは若干怪しいと自分でも思っているので、もっと正しいやり方があったら知りたい。

また、なんとなく次期バージョンだともうちょっと扱いやすくなってそうなので、それまで待ってもいいのかもしれない。

22
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
15