ES-Module in Service Workern

Eine moderne Alternative zu importScripts().

Hintergrund

ES-Module sind schon seit einiger Zeit bei Entwicklern beliebt. Neben einer Reihe anderer Vorteile bieten sie ein universelles Modulformat, in 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 gerade zunehmend unterstützt.

In diesem Artikel wird der aktuelle Stand der ES-Modulunterstützung in Service Workern in gängigen Browsern beschrieben. Außerdem werden einige Fallstricke aufgeführt, die Sie vermeiden sollten, und Best Practices für die Bereitstellung abwärtskompatiblen Service Worker-Codes.

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.

Vor ES-Modulen musste man zum Teilen von Code auf ältere „universelle“ Modulformate wie UMD zurückgreifen, die unnötige Boilerplate-Texte enthalten, und Code schreiben, der Änderungen an global freigegebenen Variablen vornahm.

Scripts, die über ES-Module importiert werden, können den Aktualisierungsvorgang 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 mithilfe 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 Nutzung von importScripts(). Dynamische Aufrufe von importScripts() funktionieren nicht innerhalb eines Service Workers. Alle importScripts()-Aufrufe, die grundsätzlich synchron sind, müssen abgeschlossen sein, bevor der Service Worker die install-Phase abschließt. Durch diese Einschränkung weiß der Browser, welcher JavaScript-Code für die Implementierung eines Service Workers während der Installation erforderlich ist, und kann ihn implizit im Cache speichern.

Diese Einschränkung wird möglicherweise aufgehoben und der Import dynamischer ES-Module könnte erlaubt werden. Verwenden Sie die statische Syntax vorerst nur innerhalb eines Service Workers.

Wie sieht es mit anderen Arbeitnehmern 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 gemeinsam genutzten Workern seit Version 83. Derzeit wird dies jedoch von keinem anderen Browser unterstützt.

Importkarten werden nicht unterstützt

Mit Importzuordnungen können in Laufzeitumgebungen Modulspezifier neu geschrieben werden, 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 Importkarten, können aber derzeit nicht mit Dienstarbeitern verwendet werden.

Unterstützte Browser

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

Safari unterstützt diese Funktion seit der Technologievorschau 122. Entwickler können davon ausgehen, dass sie in Zukunft in der stabilen Version von Safari verfügbar sein wird.

Beispielcode

Dies ist ein einfaches Beispiel für die Verwendung eines freigegebenen ES-Moduls im window-Kontext einer Webanwendung und die Registrierung eines Dienstarbeiters, 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. Das ist aber derzeit nicht der Fall.

Um auch Browser ohne integrierte Unterstützung zu berücksichtigen, 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 zu importierenden Module bereits im IIFE- oder UMD-Format verfügbar sind, können Sie sie mit importScripts() importieren.

Sobald zwei Versionen Ihres Service Workers verfügbar sind – eine mit ES-Modulen und die andere nicht – 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 noch in der Entwicklung. Empfehlungen finden Sie in der Diskussion in diesem GitHub-Problem.

_Foto von Vlado Paunovic auf Unsplash_