Moduli ES nei service worker

Un'alternativa moderna a importScripts().

Sfondo

I moduli ES sono da tempo i preferiti degli sviluppatori. Oltre a un numero di altri vantaggi, offrono la promessa di un formato di modulo universale in cui il codice condiviso può essere rilasciato una volta ed eseguito nei browser e in runtime alternativi come Node.js. Sebbene tutti i browser moderni offrano un certo supporto per i moduli ES, non tutti offrono il supporto ovunque sia possibile eseguire il codice. In particolare, il supporto per l'importazione di moduli ES all'interno del service worker di un browser sta iniziando a diventare più ampiamente disponibile.

Questo articolo descrive in dettaglio lo stato attuale del supporto dei moduli ES nei service worker nei browser più comuni, oltre ad alcune insidie da evitare e alle best practice per la distribuzione di codice dei service worker compatibile con le versioni precedenti.

Casi d'uso

Il caso d'uso ideale per i moduli ES all'interno dei service worker è il caricamento di una libreria moderna o di un codice di configurazione condiviso con altri runtime che supportano i moduli ES.

Prima dei moduli ES, il tentativo di condividere il codice in questo modo comportava l'utilizzo di formati di moduli "universali" meno recenti, come UMD, che includono boilerplate non necessari, e la scrittura di codice che apportava modifiche alle variabili esposte a livello globale.

Gli script importati tramite i moduli ES possono attivare il flusso di aggiornamento del service worker se i loro contenuti cambiano, in linea con il comportamento di importScripts().

Limitazioni attuali

Solo importazioni statiche

I moduli ES possono essere importati in uno dei due modi seguenti: staticamente, utilizzando la sintassi import ... from '...', o dinamicamente, utilizzando il metodo import(). All'interno di un service worker, al momento è supportata solo la sintassi statica.

Questa limitazione è analoga a una restrizione simile applicata all'utilizzo di importScripts(). Le chiamate dinamiche a importScripts() non funzionano all'interno di un service worker e tutte le chiamate importScripts(), che sono intrinsecamente sincrone, devono essere completate prima che il service worker completi la fase install. Questa limitazione garantisce che il browser sia a conoscenza di tutto il codice JavaScript necessario per l'implementazione di un service worker durante l'installazione e sia in grado di memorizzarlo implicitamente nella cache.

In futuro, questa limitazione potrebbe essere revocata e le importazioni dinamiche di moduli ES potrebbero essere consentite. Per il momento, assicurati di utilizzare solo la sintassi statica all'interno di un service worker.

E gli altri lavoratori?

Il supporto dei moduli ES nei worker "dedicati", ovvero quelli creati con new Worker('...', {type: 'module'}), è più diffuso ed è supportato in Chrome ed Edge dalla versione 80, nonché dalle versioni recenti di Safari. Le importazioni di moduli ES statici e dinamici sono supportate nei worker dedicati.

Chrome ed Edge supportano i moduli ES negli shared worker dalla versione 83, ma nessun altro browser offre questo supporto al momento.

Nessun supporto per le mappe di importazione

Le mappe di importazione consentono agli ambienti di runtime di riscrivere gli identificatori dei moduli, ad esempio per anteporre l'URL di una CDN preferita da cui caricare i moduli ES.

Sebbene Chrome ed Edge versione 89 e successive supportino le mappe di importazione, al momento non possono essere utilizzate con i service worker.

Supporto browser

I moduli ES nei service worker sono supportati in Chrome ed Edge a partire dalla versione 91.

Safari ha aggiunto il supporto nella versione Technology Preview 122 e gli sviluppatori dovrebbero aspettarsi di vedere questa funzionalità rilasciata nella versione stabile di Safari in futuro.

Esempio di codice

Questo è un esempio di base di utilizzo di un modulo ES condiviso nel contesto window di un'app web, registrando al contempo un service worker che utilizza lo stesso modulo 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);
    // ...
  })());
});

Compatibilità con le versioni precedenti

L'esempio precedente funzionerebbe correttamente se tutti i browser supportassero i moduli ES nei service worker, ma al momento della stesura di questo articolo non è così.

Per supportare i browser che non dispongono di supporto integrato, puoi eseguire lo script del service worker tramite un bundler compatibile con i moduli ES per creare un service worker che includa tutto il codice del modulo in linea e funzioni nei browser meno recenti. In alternativa, se i moduli che stai tentando di importare sono già disponibili in bundle nei formati IIFE o UMD, puoi importarli utilizzando importScripts().

Una volta che hai a disposizione due versioni del service worker, una che utilizza i moduli ES e l'altra no, devi rilevare ciò che è supportato dal browser corrente e registrare lo script del service worker corrispondente. Le best practice per il rilevamento del supporto sono attualmente in fase di evoluzione, ma puoi seguire la discussione in questo problema di GitHub per i consigli.