Service Worker の ES モジュール

importScripts() の最新の代替手段。

背景情報

ES モジュールは、以前からデベロッパーの方の間で人気です。その他の多くのメリットに加えて、共有コードを一度リリースすると、ブラウザや Node.js などの代替ランタイムで実行できるユニバーサル モジュール形式が約束されます。最新のすべてのブラウザは ES モジュールをサポートしていますが、コードを実行できるあらゆる場所をサポートしているわけではありません。具体的には、ブラウザの Service Worker 内での ES モジュールのインポートのサポートが広く利用可能になり始めたところです。

この記事では、一般的なブラウザでの Service Worker における ES モジュールのサポートの現状、回避すべき問題点、下位互換性のある Service Worker コードをリリースするためのベスト プラクティスについて詳しく説明します。

ユースケース

Service Worker 内の ES モジュールの理想的なユースケースは、ES モジュールをサポートする他のランタイムと共有される最新のライブラリまたは構成コードを読み込む場合です。

ES モジュールの前にこの方法でコードを共有しようとすると、不要なボイラープレートを含む UMD などの古い「ユニバーサル」モジュール形式を使用し、グローバルに公開されている変数を変更するコードを記述する必要がありました。

ES モジュールを介してインポートされたスクリプトは、importScripts()動作に合わせて、内容が変更された場合に Service Worker の更新フローをトリガーできます。

現在の制限事項

静的インポートのみ

ES モジュールは、import ... from '...' 構文を使用して静的にインポートするか、import() メソッドを使用して動的にインポートできます。現在、Service Worker 内では静的な構文のみがサポートされています。

この制限は、importScripts() の使用に適用される同様の制限に似ています。importScripts() の動的呼び出しは Service Worker 内では機能しません。本質的に同期的な importScripts() 呼び出しはすべて、Service Worker が install フェーズを完了する前に完了する必要があります。この制限により、Service Worker の実装に必要な JavaScript コードをブラウザがインストール時に認識し、暗黙的にキャッシュできるようになります。

最終的にはこの制限が解除され、動的 ES モジュールのインポートが許可される可能性があります。現時点では、Service Worker 内の静的構文のみを使用してください。

他のワーカーについてはどうですか?

「専用」ワーカーでの ES モジュールnew Worker('...', {type: 'module'}) で構築されたワーカー)のサポートはより広く行われており、Chrome と Edge のバージョン 80 以降、および Safari の最近のバージョンでサポートされています。専用ワーカーでは、ES モジュールの静的インポートと動的インポートの両方がサポートされています。

Chrome と Edge は、バージョン 83 以降、共有ワーカーで ES モジュールをサポートしてきましたが、現時点では他のブラウザはサポートしていません。

地図のインポートはサポート対象外

マップのインポートを使用すると、ランタイム環境でモジュール指定子を書き換えることができます。たとえば、ES モジュールの読み込み元となる優先 CDN の URL を先頭に追加できます。

Chrome と Edge のバージョン 89 以降では、地図のインポートがサポートされていますが、現時点では Service Worker で使用できません

ブラウザ サポート

Service Worker の ES モジュールは、バージョン 91 以降、Chrome と Edge でサポートされています。

Safari は Technology Preview 122 リリースでサポートを追加しました。この機能は今後、Safari の安定版でリリースされる予定です。

コード例

ウェブアプリの window コンテキストで共有 ES モジュールを使用し、同じ ES モジュールを使用する Service Worker も登録する基本的な例を次に示します。

// Inside config.js:
export const cacheName = 'my-cache';
// Inside your web app:
<script type="module">
  import {cacheName} from './config.js';
  // Do something with cacheName.

  await navigator.serviceWorker.register('es-module-sw.js', {
    type: 'module',
  });
</script>
// Inside es-module-sw.js:
import {cacheName} from './config.js';

self.addEventListener('install', (event) => {
  event.waitUntil((async () => {
    const cache = await caches.open(cacheName);
    // ...
  })());
});

下位互換性

上の例は、すべてのブラウザで Service Worker の ES モジュールがサポートされていれば問題なく機能しますが、今回はそうではありません。

サポートが組み込まれていないブラウザに対応するために、ES モジュール互換バンドラを介して Service Worker スクリプトを実行し、すべてのモジュール コードをインラインで含み、古いブラウザでも動作する Service Worker を作成できます。また、インポートしようとしているモジュールが IIFE 形式または UMD 形式ですでにバンドルされている場合は、importScripts() を使用してインポートできます。

Service Worker の 2 つのバージョン(ES モジュールを使用するバージョンと使用しないバージョン)を用意したら、現在のブラウザでサポートされているバージョンを検出し、対応する Service Worker スクリプトを登録する必要があります。サポートを検出するためのベスト プラクティスは随時更新されていますが、推奨事項については、こちらの GitHub の問題で説明されています。

_写真撮影: Vlado PaunovicUnsplash より