PWA: Workboxドキュメンテーション和訳


PWA界隈がアツくなってきているので、Service Worker周りをよしなに設定してくれるWorkboxの勉強も兼ねて、オフィシャルのドキュメントを和訳してみようと思いました。とりあえず第一弾です。
間違っている部分などあればご指摘いただけますと幸いです。下記諸々の設定はWorkbox-CLIやwebpackなどのツールを使用すれば、一つ一つ手で打っていく必要はなさそうですが、裏側で何がおきているかは把握しておきたいですね。
コツコツ更新してゆく予定です。

  1. Workboxとは何か?
  2. Workbox Core 和訳
  3. PWA: Workbox Precaching 和訳(その1)
  4. PWA: Workbox Precaching 和訳(その2)
  5. PWA: Workbox Routing 和訳 (その1)
  6. PWA: Workbox Routing 和訳 (その2)
  7. PWA: Workbox Strategies 和訳
  8. PWA: Workbox Cache Expiration 和訳
  9. PWA: Workbox Background Sync 和訳

参照元ページ: https://developers.google.com/web/tools/workbox/modules/workbox-sw

Workboxとは何か?

Workboxはworkbox-swというコアモジュールから成っていて、Workboxモジュールの読み込みやヘルパーメソッドをとても簡単に提供してくれます。


export class WorkboxSW {
  //__(略)__
  /**
   * Updates the configuration options. You can specify whether to treat as a
   * debug build and whether to use a CDN or a specific path when importing
   * other workbox-modules
   * デバッグモードの設定やWorkboxモジュールをCDN経由又は特定のパスからインポートする設定をします。
   */
  setConfig(options = {}) {
    if (!this._modulesLoaded) {
      Object.assign(this._options, options);
      this._env = this._options.debug ? 'dev' : 'prod';
    } else {
      throw new Error('Config must be set before accessing workbox.* modules');
    }
  }

  /**
   * Load a Workbox module by passing in the appropriate module name.
   * 引数にモジュール名を渡すことでWorkboxモジュールを読み込ませます。
   * 
   * This is not generally needed unless you know there are modules that are
   * dynamically used and you want to safe guard use of the module while the
   * user may be offline.
   * 動的に使用されるモジュールがあり、ユーザーがオフライン時にモジュールを安全に使用したい場合を除いて、これは通常必要ありません。
   */
  loadModule(moduleName) {
    const modulePath = this._getImportPath(moduleName);
    try {
      importScripts(modulePath);
      this._modulesLoaded = true;
    } catch (err) {
      console.error(
          `Unable to import module '${moduleName}' from '${modulePath}'.`);
      throw err;
    }
  }

  /**
   * This method will get the path / CDN URL to be used for importScript calls.
   * このメソッドはimportScriptで呼ぶパスを取得します。
   */
  _getImportPath(moduleName) {
    if (this._options.modulePathCb) {
      return this._options.modulePathCb(moduleName, this._options.debug);
    }

    // TODO: This needs to be dynamic some how.
    let pathParts = [CDN_PATH];

    const fileName = `${moduleName}.${this._env}.js`;

    const pathPrefix = this._options.modulePathPrefix;
    if (pathPrefix) {
      pathParts = pathPrefix.split('/');
      if (pathParts[pathParts.length - 1] === '') {
        pathParts.splice(pathParts.length - 1, 1);
      }
    }
    pathParts.push(fileName);
    return pathParts.join('/');
  }
}

Workboxを読み込ませる

最も簡単にworkbox-swをCDN経由で読み込むには、あなたのservice-worker.jsに下記のように記述するだけです。

service-worker.js(ファイル名は任意)


importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-sw.js');

例えば、あなたのservice-worker.js内に下記の記述をすることで、workboxの名前空間を使用でき、すべてのWorkboxモジュールにアクセスできるようになります。


workbox.precaching.*,
workbox.routing.*

初めてモジュールを参照するとき、workbox-swは上記記述を元にWorkboxモジュールを読み込みます。何のモジュールが読み込まれているかはDevtoolのNetworkタブを見ればわかります。


ちなみにローカルから読み込ませる場合は下記のように記述します。

importScripts('/third_party/workbox/workbox-sw.js');

workbox.setConfig({
  modulePathPrefix: '/third_party/workbox/'
});

CDNの代わりにローカルからWorkboxファイルを読み込ませる場合

(もしあなたがCDNを利用せず)、ご自分のドメイン(サーバー)からWorkboxファイルを使用するのはとても簡単です。
最も簡単な方法は下記の通りです。
1. workbox-clicopyLibraryコマンドから、もしくはGitHubからファイルを落としてきます
2. modulePathPrefixのconfigのオプションからworkbox-swにファイルのパスを渡します

もし/third_party/workbox/下にファイルを置いた場合の読み込み方法は下記のような記述になります。


importScripts('/third_party/workbox/workbox-sw.js');

workbox.setConfig({
  modulePathPrefix: '/third_party/workbox/'
});

非同期インポートを避ける

重要なことは、初めて新しいモジュールを読み込む時は、(CDNもしくはローカルのURLの)JavaScriptファイルのパスが渡されたimportScripts()を呼ぶことです。いずれにしても注意すべき制限が適用されます。importScripts()はService Workerのinstallイベント時、もしくはサービスワーカーの同期的な初期化の際に暗黙に呼ばれることです。

このルールを犯さないためのベストプラクティスは、workbox.*の名前空間の参照をイベントハンドラーや非同期関数の外におくことです。

例えば、下記のコードは問題ありません。


importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.6.1/workbox-sw.js');

workbox.routing.registerRoute(
    new RegExp('\.png$'),
    workbox.strategies.cacheFirst()
);

しかし下記のコードは問題になります。もしあなたがworkbox.strategiesを移動させなければ(fetchイベントハンドラー内に記述されているため)


importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-sw.js');

self.addEventListener('fetch', (event) => {
  if (event.request.url.endsWith('.png')) {
    // Oops! This causes workbox-strategies.js to be imported inside a fetch handler,
    // outside of the initial, synchronous service worker execution.
    const cacheFirst = new workbox.strategies.CacheFirst();
    event.respondWith(cacheFirst.makeRequest({request: event.request}));
  }
});

もしルールを守りたいのであれば、workbox.loadModule()を使って明示的にimportScripts()をイベントハンドラーの外で呼ぶ必要があります。

もしくは、イベントハンドラーの外でworkboxの参照をすることでも問題を避けることができます。


importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-sw.js');

// This will trigger the importScripts() for workbox.strategies and its dependencies:
workbox.loadModule('workbox-strategies');

self.addEventListener('fetch', (event) => {
  if (event.request.url.endsWith('.png')) {
    // Referencing workbox.strategies will now work as expected.
    const cacheFirst = new workbox.strategies.CacheFirst();
    event.respondWith(cacheFirst.makeRequest({request: event.request}));
  }
});

*Chromeのバージョンによってはルールを上記ツールを守らなくても動く場合がありますが、これはお勧めしません。他のブラウザでも実装されているように名前空間の参照先をイベントハンドラーの外側でのみ有効にする実装がChromeでも予定されています。

デバッグモードとプロダクションモードを強制する

全てのWorkboxモジュールは2つのビルド方法を持っています。
1. ログの出力、型チェックをするデバッグモード
2. ログは出さず、型チェックをしないプロダクションモード

デフォルトでworkbox-swはlocalhost上ではデバッグモードにセットされていますが、それ以外のオリジンではプロダクションモードが使用されます。

もし強制的にデバッグモードを有効にしたい場合は、setConfigでdebugの値を変更すると良いです。


workbox.setConfig({
    debug:
})