Service Worker の ES モジュール

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

背景

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

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

ユースケース

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

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

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

現在の制限事項

静的インポートのみ

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

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

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

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

new Worker('...', {type: 'module'}) で構築された「専用」ワーカーの ES モジュールのサポートはより広範にわたっており、バージョン 80 以降の Chrome と Edge、および 最新バージョンの 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);
    // ...
  })());
});

下位互換性

上記の例は、すべてのブラウザがサービス ワーカーの ES モジュールをサポートしている場合は正常に動作しますが、現時点ではそうではありません。

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

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

_写真: Vlado PaunovicUnsplash)_