Modules ES dans les service workers

Alternative moderne à importScripts().

Contexte

Les modules ES sont depuis longtemps les favoris des développeurs. En plus d'un certain nombre d'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 modernes acceptent certains 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 dans le service worker d'un navigateur commence à se répandre.

Cet article décrit l'état actuel de la prise en charge des modules ES dans les services workers dans les navigateurs courants, ainsi que quelques points à éviter et des bonnes pratiques pour publier 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, toute tentative de partage de code de cette manière impliquait d'utiliser d'anciens formats de module "universels" tels que UMD, qui incluent du code standard inutile, et d'écrire du code qui modifiait les variables exposées globalement.

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 : 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 prise en charge.

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 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.

Cette restriction pourra éventuellement être levée, et les importations de modules ES dynamiques pourront être autorisées. Pour le moment, assurez-vous de 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 et 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 prises en charge dans 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 le propose 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, 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 à partir de la version 89 acceptent les cartes d'importation, ils ne peuvent actuellement pas être utilisés avec les workers de service.

Prise en charge des navigateurs

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

Safari est désormais compatible avec la version Preview technologique 122, et les développeurs doivent s'attendre à ce que cette fonctionnalité soit publiée dans la version stable de Safari à l'avenir.

Exemple de code

Voici un exemple basique 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 parfaitement si tous les navigateurs acceptaient les modules ES dans les services workers, mais ce n'est pas le cas au moment de la rédaction de cet article.

Pour prendre en charge les navigateurs qui ne sont pas compatibles avec les services intégrés, vous pouvez exécuter votre script de service worker via un outil de compilation compatible avec les modules ES afin de créer un service worker qui inclut tout le code du module en ligne et qui fonctionnera dans les anciens navigateurs. Si les modules que vous essayez d'importer sont déjà disponibles dans les formats IIFE ou UMD, vous pouvez les importer à l'aide de importScripts().

Une fois que vous avez deux versions de votre service worker disponibles (l'une qui utilise des modules ES et l'autre qui ne le fait pas), vous devez détecter ce que le navigateur actuel prend en charge et enregistrer le script de service worker correspondant. Les bonnes pratiques de détection de la compatibilité sont en cours d'élaboration, mais vous pouvez suivre la discussion dans ce problème GitHub pour obtenir des recommandations.

_Photo par Vlado Paunovic sur Unsplash_