ES-Module in Service Workern

Eine moderne Alternative zu importScripts().

Hintergrund

ES-Module sind bei Entwicklern schon seit einiger Zeit beliebt. Neben einer Reihe weiterer Vorteile bieten sie ein universelles Modulformat, bei dem freigegebener Code einmal veröffentlicht und in Browsern sowie in alternativen Laufzeiten wie Node.js ausgeführt werden kann. Obwohl alle modernen Browser in gewissem Umfang ES-Module unterstützen, bieten sie nicht überall, wo Code ausgeführt werden kann. Insbesondere die Unterstützung für das Importieren von ES-Modulen in den Service Worker eines Browsers wird nach und nach allgemein verfügbar gemacht.

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 Probleme beschrieben, die Sie vermeiden sollten, sowie Best Practices für den Versand abwärtskompatibler Service Worker-Code.

Anwendungsfälle

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

Der Versuch, Code auf diese Weise vor ES-Modulen freizugeben, erforderte die Verwendung älterer "universeller" Modulformate wie UMD, die nicht benötigte Boilerplate enthalten, und das Schreiben von Code, der Änderungen an global zugänglichen Variablen bewirkte.

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

Aktuelle Beschränkungen

Nur statische Importe

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

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

Möglicherweise wird diese Einschränkung später aufgehoben und dynamische ES-Modulimporte sind zulässig. Vorerst sollten Sie darauf achten, dass Sie nur die statische Syntax innerhalb eines Service Workers verwenden.

Und was ist mit anderen Mitarbeitern?

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

Chrome und Edge unterstützen ES-Module in gemeinsam genutzten Workern seit Version 83. Dies ist jedoch derzeit von keinem anderen Browser möglich.

Keine Unterstützung für den Import von Karten

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

Obwohl Chrome und Edge ab Version 89 Importkarten unterstützen, können sie derzeit 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 unterstützt jetzt die Version 122 der Technologievorschau. Entwickler sollten diese Funktion künftig in der stabilen Version von Safari veröffentlichen werden.

Beispielcode

Dies ist ein einfaches Beispiel für die Verwendung eines freigegebenen ES-Moduls im window-Kontext einer Webanwendung, während gleichzeitig 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 einwandfrei funktionieren, wenn alle Browser ES-Module in Service-Workern unterstützen würden. Zum jetzigen Zeitpunkt ist dies jedoch nicht der Fall.

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

Sobald zwei Versionen Ihres Service Workers verfügbar sind – eine mit ES-Modulen und eine andere ohne – müssen Sie ermitteln, was der aktuelle Browser unterstützt, und das entsprechende Service Worker-Skript registrieren. Die Best Practices zur Erkennung von Unterstützung befinden sich derzeit im Wandel. Empfehlungen finden Sie in den Empfehlungen in diesem GitHub-Problem.

_Foto von Vlado Paunovic auf Unsplash_