Modules ES dans les service workers

Une alternative moderne à importScripts().

Arrière-plan

Les modules ES sont très appréciés des développeurs depuis un certain temps déjà. En plus de nombreux autres avantages, ils offrent la promesse d'un format de module universel dans lequel le code partagé peut être publié une seule fois et exécuté dans les 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 compatibilité avec l'importation de modules ES dans le service worker d'un navigateur commence tout juste à se généraliser.

Cet article décrit l'état actuel de la compatibilité des modules ES dans les service workers sur les navigateurs courants, ainsi que certains pièges à éviter et les bonnes pratiques pour fournir du code de service worker rétrocompatible.

Cas d'utilisation

Le cas d'utilisation idéal des modules ES dans les 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.

Avant les modules ES, pour partager du code de cette manière, il fallait utiliser des formats de module "universels" plus anciens, comme UMD, qui incluent du code récurrent inutile, et écrire du code qui modifiait les variables exposées de manière globale.

Les scripts importés via des 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 deux manières : soit de manière statique, à l'aide de la syntaxe import ... from '...', soit de manière dynamique, à l'aide de la méthode import(). Dans un service worker, seule la syntaxe statique est actuellement prise en charge.

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

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

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

La compatibilité avec les modules ES dans les workers "dédiés" (ceux construits avec new Worker('...', {type: 'module'})) est plus répandue. Elle est disponible dans Chrome et Edge depuis la version 80, ainsi que dans les versions récentes de Safari. Les importations de modules ES statiques et dynamiques sont compatibles avec les workers dédiés.

Chrome et Edge sont compatibles avec les modules ES dans les workers partagés depuis la version 83, mais aucun autre navigateur ne propose cette compatibilité pour le moment.

Pas de compatibilité avec les mappages d'importation

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

Bien que Chrome et Edge version 89 et ultérieures soient compatibles avec les mappages d'importation, ils ne peuvent actuellement pas être utilisés avec les service workers.

Prise en charge des navigateurs

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

Safari a ajouté la compatibilité dans la version Technology Preview 122, et les développeurs devraient voir cette fonctionnalité publiée dans la version stable de Safari à l'avenir.

Exemple de code

Voici un exemple simple d'utilisation d'un module ES partagé dans le contexte window d'une application Web, tout en enregistrant un service worker qui utilise 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 à l'heure où nous écrivons ces lignes.

Pour prendre en charge les navigateurs qui ne sont pas compatibles de manière intégrée, vous pouvez exécuter votre script de service worker via un bundleur compatible avec les modules ES afin de créer un service worker qui inclut l'intégralité du code du module de manière intégrée et qui fonctionnera dans les anciens navigateurs. Si les modules que vous essayez d'importer sont déjà disponibles au format IIFE ou UMD, vous pouvez les importer à l'aide de importScripts().

Une fois que vous disposez de deux versions de votre service worker (l'une utilisant des modules ES et l'autre non), vous devez détecter ce que le navigateur actuel accepte et enregistrer le script de service worker correspondant. Les bonnes pratiques pour détecter la compatibilité sont actuellement en évolution, mais vous pouvez suivre la discussion dans ce problème GitHub pour obtenir des recommandations.