Modules ES dans les service workers

Alternative moderne à importScripts().

Contexte

Les modules ES sont appréciés des développeurs depuis un certain temps. En plus de nombreux autres avantages, elles offrent la promesse d'un format de module universel dans lequel le code partagé peut être publié une seule fois et s'exécuter dans des navigateurs et dans d'autres environnements d'exécution tels que Node.js. Bien que tous les navigateurs récents soient compatibles avec les modules ES, ils ne sont pas tous compatibles partout où le code peut être exécuté. Plus précisément, la prise en charge de l'importation de modules ES à l'intérieur d'un service worker de navigateur commence tout juste à se généraliser.

Cet article présente l'état actuel de la compatibilité du module ES avec les service workers sur les navigateurs courants, ainsi que les problèmes à éviter. Il décrit également les bonnes pratiques à suivre pour envoyer du code de service worker rétrocompatible.

Cas d'utilisation

Le cas d'utilisation idéal des modules ES à l'intérieur des service workers consiste à charger une bibliothèque moderne ou un code de configuration partagé avec d'autres environnements d'exécution compatibles avec les modules ES.

Les tentatives de partage de code de cette manière avant les modules ES impliquaient l'utilisation d'anciens formats de modules "universels" tels que UMD, qui incluent un code récurrent inutile, et l'écriture de code modifiant les variables exposées au niveau mondial.

Les scripts importés via les modules ES peuvent déclencher le flux de mise à jour du service worker si leur contenu change, ce qui correspond au comportement de importScripts().

Limites actuelles

Importations statiques uniquement

Les modules ES peuvent être importés de l'une des deux manières suivantes: statiquement, à l'aide de la syntaxe import ... from '...', ou dynamiquement, à l'aide de la méthode import(). Dans un service worker, seule la syntaxe statique est actuellement acceptée.

Cette limitation est analogue à une restriction similaire appliquée à l'utilisation de importScripts(). Les appels dynamiques à importScripts() ne fonctionnent pas dans un service worker et tous les appels importScripts(), qui sont par nature synchrones, doivent être terminés avant que le service worker termine sa phase install. Cette restriction garantit que le navigateur connaît et peut mettre implicitement en cache tout le code JavaScript nécessaire à l'implémentation d'un service worker lors de l'installation.

À terme, cette restriction peut être levée, et les importations de modules ES dynamiques peuvent être autorisées. Pour l'instant, veillez à n'utiliser la syntaxe statique que dans un service worker.

Qu'en est-il des autres nœuds de calcul ?

La prise en charge des modules ES dans des nœuds de calcul "dédiés" (ceux créés avec new Worker('...', {type: 'module'})) est plus répandue. Elle est prise en charge par Chrome et Edge depuis la version 80, ainsi que les versions récentes de Safari. Les importations de modules ES statiques et dynamiques sont compatibles avec les nœuds de calcul dédiés.

Chrome et Edge sont compatibles avec les modules ES des nœuds de calcul partagés depuis la version 83, mais aucun autre navigateur n'offre de prise en charge pour le moment.

Impossible d'importer des cartes

Les mappages d'importation permettent aux environnements d'exécution de réécrire les spécificateurs de module pour, par exemple, ajouter l'URL d'un CDN préféré à partir duquel les modules ES peuvent être chargés.

Bien que la version 89 ou ultérieure de Chrome et Edge accepte l'importation de cartes, elles ne peuvent pas être utilisées avec les service workers pour le moment.

Prise en charge des navigateurs

Les modules ES des service workers sont compatibles avec Chrome et Edge à partir de la version 91.

Safari a ajouté la prise en charge dans la version 122 de Technology Preview. Les développeurs devraient voir cette fonctionnalité disponible dans la version stable de Safari à l'avenir.

Exemple de code

Voici un exemple de base d'utilisation d'un module ES partagé dans le contexte window d'une application Web, tout en enregistrant un service worker utilisant le même module 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);
    // ...
  })());
});

Rétrocompatibilité

L'exemple ci-dessus fonctionnerait correctement si tous les navigateurs étaient compatibles avec les modules ES dans les service workers, mais ce n'est pas le cas au moment de la rédaction de cet article.

Pour les navigateurs non compatibles, vous pouvez exécuter votre script de service worker via un bundler compatible avec le module ES afin de créer un service worker intégrant tout le code du module et qui fonctionnera dans les anciens navigateurs. Si les modules que vous essayez d'importer sont déjà disponibles regroupés aux formats IIFE ou UMD, vous pouvez également les importer à l'aide de importScripts().

Une fois que deux versions de votre service worker sont disponibles, l'une utilisant des modules ES et l'autre non, vous devez détecter la compatibilité du navigateur actuel et enregistrer le script de service worker correspondant. Les bonnes pratiques pour détecter une demande d'assistance évoluent actuellement, mais vous pouvez suivre la discussion sur cette question GitHub pour obtenir des recommandations.

_Photo par Vlado Paunovic sur Unsplash_