zukucode
主にWEB関連の情報を技術メモとして発信しています。

sw-precacheでWEBアプリのオフライン対応をする

service workerでキャッシュコントロールを行い、オフラインでも閲覧できる状態にできます。

キャッシュコントロールを独自に実装するのは難易度が高く、実装を誤るとF5でのリロードでも更新されなくなってしまいます。

sw-precacheというパッケージを使えば自動でservice workerのファイルを作成してくれますので、これを使用してオフライン対応を行います。

パッケージのインストール

最初にsw-precacheのパッケージをインストールします。

$ npm install --save-dev sw-precache

実装

sw-precacheの処理を実装します。今回はgulpsw-precache用のタスクを作成しました。

フォルダ構成は以下とします。

  • public (公開フォルダ)
    • css (cssファイルを配置)
    • img (画像ファイルを配置)
    • js (jsファイルを配置)
    • index.html
  • gulpfile.js
  • package.json
gulpfile.js
const dest = './public';
gulp.task('sw', () => {
  return swPrecache.write(dest + '/service-worker.js', {
    stripPrefix: dest,
    staticFileGlobs: [
      dest + '/css/*',
      dest + '/img/*',
      dest + '/js/*',
      dest + 'index.html'
    ]
  });
});

タスクを実行して作成されたservice-worker.jsをサーバーにアップしてloadイベントで読み込みます。

window.addEventListener('load', function () {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js', { scope: '/' });
  }
});

キャッシュするファイルの指定

staticFileGlobsでキャッシュするファイルを指定します。

service workerでファイルを指定する場合、例えばhttp://example.com/js/app.jsのファイルを指定するときは/js/app.jsと指定します。

上記タスクではpublic/js/app.jsという形でファイルを指定するため、service workerで指定されるパスが/public/js/app.jsになってしまいます。

/publicを消したいので、stripPrefixのオプションでstripPrefix: deststripPrefix: './public')と指定しています。

staticFileGlobsで指定されたファイルは一度キャッシュされるとそれ以降はサーバーのファイルを見に行かず、キャッシュされたファイルを参照するようになります。(F5でリロードしてもキャッシュされたファイルを参照します。)

これによりオフラインの環境でもキャッシュされたファイルを参照し、オフラインでの閲覧が可能になります。

ファイルを更新した場合

service worker導入後にjs/app.jsを修正したとします。修正したファイルをサーバーにアップしても、service workerが動作しているため、サーバーのファイルではなく、キャッシュされたファイル(修正前のファイル)が参照されてしまいます。

再度サーバーのファイルを取得して、そのファイル(修正したファイル)をキャッシュしたい場合は、service workerのファイルで指定しているバージョンを変更する必要があります。

js/app.jsを修正したあとにsw-precacheを実行するとバージョンが変更されたservice workerのファイルが作成されます。

生成されたservice workerのファイルをサーバーにアップしてアクセスすると、変更したjs/app.jsだけサーバーから取得し、修正した内容が反映されます。

動的ページの場合

index.htmlではなく、phpなどで動的に生成している場合は以下のようにdynamicUrlToDependenciesを指定します。

gulpfile.js
const dest = './public';
gulp.task('sw', () => {
  return swPrecache.write(dest + '/service-worker.js', {
    stripPrefix: dest,
    staticFileGlobs: [
      dest + '/css/*',
      dest + '/img/*',
      dest + '/js/*'
    ],
    dynamicUrlToDependencies: {
      '/': [
        './page/app.php',
        './page/header.php',
        './page/footer.php'
      ]
    }
  });
});

/にアクセスした時、レスポンスされた内容がキャッシュされます。

page/app.phppage/header.phppage/footer.phpのいづれかが変更されたときに、再度service workerを更新すると、変更した/にアクセスした時、サーバーから取得して再びその内容をキャッシュします。

注意点

動的ページのキャッシュは注意が必要です。

例えばphpでユーザー情報を動的に表示している場合、一度レスポンスされてキャッシュされてしまうと、ユーザー情報を更新して再表示しても内容が反映されません。(再表示してもphpの処理がサーバーで行われるのではなくキャッシュした情報を表示するだけなので)

実装効率や保守性の観点でindex.htmlを複数のファイルに分けたのなら問題ありませんが、ログインユーザーの情報や操作手順によってレスポンスされる内容が動的に変わってしまうような場合はキャッシュが難しいので、動的なページを生成する処理はすべてJavaScriptで行うように修正するなどの対応が必要になります。


関連記事