ES-Module in Service Workern

Eine moderne Alternative zu importScripts().

Hintergrund

ES-Module sind bei Entwicklern schon seit einiger Zeit beliebt. Neben einer Reihe anderer Vorteile bieten sie ein universelles Modulformat, bei dem freigegebener Code einmal veröffentlicht und in Browsern und alternativen Laufzeiten wie Node.js ausgeführt werden kann. Alle modernen Browser bieten zwar eine gewisse Unterstützung für ES-Module, aber nicht überall, wo Code ausgeführt werden kann. Insbesondere die Unterstützung für den Import von ES-Modulen in den Service Worker eines Browsers wird erst jetzt immer häufiger verfügbar.

In diesem Artikel wird der aktuelle Stand der Unterstützung von ES-Modulen in Service Workern in gängigen Browsern beschrieben. Außerdem werden einige Fallstricke, die Sie vermeiden sollten, und Best Practices für die Bereitstellung von abwärtskompatiblem Service Worker-Code erläutert.

Anwendungsfälle

Der ideale Anwendungsfall für ES-Module in Service Workern ist das Laden einer modernen Bibliothek oder eines Konfigurationscodes, der mit anderen Laufzeiten geteilt wird, die ES-Module unterstützen.

Vor der Einführung von ES-Modulen mussten für die Freigabe von Code ältere „universelle“ Modulformate wie UMD verwendet werden, die unnötigen Boilerplate-Code enthalten. Außerdem musste Code geschrieben werden, der Änderungen an global zugänglichen Variablen vornahm.

Über ES-Module importierte Skripts können den Aktualisierungsablauf des Service Workers auslösen, wenn sich ihr Inhalt ändert. Das entspricht dem Verhalten von importScripts().

Aktuelle Beschränkungen

Nur statische Importe

ES-Module können auf zwei Arten importiert werden: entweder statisch mit der Syntax import ... from '...' oder dynamisch mit der Methode import(). In einem Service Worker wird derzeit nur die statische Syntax unterstützt.

Diese Einschränkung ist analog zu einer ähnlichen Einschränkung für die Nutzung von importScripts(). Dynamische Aufrufe von importScripts() funktionieren nicht innerhalb eines Service Workers und alle importScripts()-Aufrufe, die von Natur aus synchron sind, müssen abgeschlossen sein, bevor der Service Worker seine install-Phase beendet. Durch diese Einschränkung wird sichergestellt, dass der Browser den gesamten JavaScript-Code kennt, der für die Implementierung eines Service Workers während der Installation erforderlich ist, und ihn implizit im Cache speichern kann.

Diese Einschränkung wird möglicherweise irgendwann aufgehoben und dynamische ES-Modulimporte werden dann eventuell zugelassen. Achten Sie vorerst darauf, dass Sie die statische Syntax nur innerhalb eines Service Workers verwenden.

Wie sieht es bei anderen Mitarbeitern aus?

Die Unterstützung für ES-Module in „dedizierten“ Workern, die mit new Worker('...', {type: 'module'}) erstellt wurden, ist weiter verbreitet und wird in Chrome und Edge seit Version 80 sowie in aktuellen Versionen von Safari unterstützt. Sowohl statische als auch dynamische ES-Modulimporte werden in dedizierten Workern unterstützt.

Chrome und Edge unterstützen ES-Module in Shared Workern seit Version 83, aber kein anderer Browser bietet derzeit Unterstützung.

Keine Unterstützung für Importzuordnungen

Mit Importzuordnungen können Laufzeitumgebungen Modulspezifizierer umschreiben, um beispielsweise die URL eines bevorzugten CDN voranzustellen, von dem die ES-Module geladen werden können.

Chrome und Edge ab Version 89 unterstützen zwar Importzuordnungen, diese können derzeit jedoch nicht mit Service Workern verwendet werden.

Unterstützte Browser

ES-Module in Service Workern werden in Chrome und Edge ab Version 91 unterstützt.

Safari hat die Unterstützung in der Technology Preview 122 hinzugefügt. Entwickler können davon ausgehen, dass diese Funktion in Zukunft in der stabilen Version von Safari veröffentlicht wird.

Beispielcode

Dies ist ein einfaches Beispiel für die Verwendung eines freigegebenen ES-Moduls im window-Kontext einer Web-App, wobei auch ein Service Worker registriert wird, der dasselbe ES-Modul verwendet:

// 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);
    // ...
  })());
});

Abwärtskompatibilität

Das obige Beispiel würde gut funktionieren, wenn alle Browser ES-Module in Service Workern unterstützen würden. Zum Zeitpunkt der Erstellung dieses Artikels ist das jedoch nicht der Fall.

Für Browser, die keine integrierte Unterstützung haben, können Sie Ihr Service Worker-Skript über einen ES-Modul-kompatiblen Bundler ausführen, um einen Service Worker zu erstellen, der den gesamten Modulcode inline enthält und in älteren Browsern funktioniert. Wenn die Module, die Sie importieren möchten, bereits im IIFE- oder UMD-Format gebündelt verfügbar sind, können Sie sie alternativ mit importScripts() importieren.

Sobald Sie zwei Versionen Ihres Service Workers haben – eine, die ES-Module verwendet, und eine, die dies nicht tut –, müssen Sie ermitteln, was der aktuelle Browser unterstützt, und das entsprechende Service Worker-Skript registrieren. Die Best Practices für die Erkennung der Unterstützung sind derzeit im Wandel. Sie können jedoch die Diskussion in diesem GitHub-Problem verfolgen, um Empfehlungen zu erhalten.