Service Worker の ES モジュール

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

背景

ES モジュールは、長い間デベロッパーに好まれてきました。その他の多くのメリットに加えて、共有コードを一度リリースして、ブラウザや Node.js などの代替ランタイムで実行できる、汎用モジュール形式のメリットも提供します。最新のすべてのブラウザは ES モジュールをサポートしていますが、コードを実行できるすべての場所をサポートしているわけではありません。具体的には、ブラウザの Service Worker 内で ES モジュールをインポートする機能が、広く利用可能になり始めています。

この記事では、一般的なブラウザでのサービス ワーカーの ES モジュール サポートの現状、回避すべき注意事項、下位互換性のあるサービス ワーカー コードを配布するためのベスト プラクティスについて詳しく説明します。

ユースケース

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

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

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

現在の制限事項

静的インポートのみ

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

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

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

他のワーカーはどうなりますか?

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

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

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

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

Chrome と Edge の バージョン 89 以降ではインポート マップがサポートされていますが、現在のところ、サービス ワーカーで使用できません

ブラウザ サポート

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

Safari では Technology Preview 122 リリースでサポートが追加されました。デベロッパーは、この機能が今後 Safari の安定版でリリースされることを想定してください。

コード例

次の例は、ウェブアプリの window コンテキストで共有 ES モジュールを使用し、同じ ES モジュールを使用するサービス ワーカーを登録する基本的な例です。

// 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 モジュール互換のバンドルツールでサービス ワーカー スクリプトを実行し、すべてのモジュールコードをインラインで含むサービス ワーカーを作成します。このサービス ワーカーは、古いブラウザでも動作します。インポートしようとしているモジュールが、IIFE 形式または UMD 形式でバンドルされている場合は、importScripts() を使用してインポートできます。

2 つのバージョンのサービス ワーカー(ES モジュールを使用するバージョンと使用しないバージョン)を用意したら、現在のブラウザでサポートされているものを検出し、対応するサービス ワーカー スクリプトを登録する必要があります。サポートを検出するためのベスト プラクティスは現在流動的ですが、この GitHub の問題のディスカッションに沿って推奨事項を確認できます。

_写真提供: Vlado PaunovicUnsplash)_