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 wird die Unterstützung für den Import von ES-Modulen in den Dienst-Worker eines Browsers erst jetzt immer weiter verbreitet.

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 in Service Workern ist das Laden einer modernen Bibliothek oder eines Konfigurationscodes, der für andere Runtimes freigegeben 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 mit der import ... from '...'-Syntax oder dynamisch mit der import()-Methode. 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 in einem Service Worker. Alle importScripts()-Aufrufe, die von Natur aus synchron sind, müssen abgeschlossen sein, bevor der Service Worker seine 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 gemeinsamen Workern seit Version 83. Kein anderer Browser bietet derzeit Unterstützung.

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 diese Funktion in Zukunft in der stabilen Version von Safari eingeführt 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.

Für Browser ohne integrierte Unterstützung können Sie Ihr Service Worker-Script über einen mit ES-Modulen 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 verfügbar sind, können Sie sie auch mit importScripts() importieren.

Sobald Sie zwei Versionen Ihres Service Workers verfügbar haben – eine, die ES-Module verwendet, und eine, die das nicht tut –, müssen Sie ermitteln, was der aktuelle Browser unterstützt, und das entsprechende Service Worker-Script registrieren. Die Best Practices für die Erkennung von Support sind derzeit in der Entwicklung. Empfehlungen finden Sie in der Diskussion in diesem GitHub-Problem.

_Foto von Vlado Paunovic auf Unsplash_