Moduli ES nei service worker

Un'alternativa moderna a importScripts().

I moduli ES sono tra i preferiti degli sviluppatori da un po' di tempo. Oltre a una serie 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. Anche se tutti i browser moderni offrono un certo supporto per i moduli ES, non tutti offrono supporto ovunque puoi eseguire il codice. Nello specifico, il supporto per l'importazione dei moduli ES all'interno del service worker di un browser sta appena iniziando a essere disponibile su larga scala.

Questo articolo descrive lo stato attuale del supporto dei moduli ES nei service worker nei browser più comuni, insieme ad alcuni suggerimenti da evitare e alle best practice per la spedizione 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.

Il tentativo di condividere il codice in questo modo prima dei moduli ES comportava l'utilizzo di formati di moduli "universali" meno recenti come UMD che includono codice boilerplate non necessario 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 worker di servizio se i relativi contenuti cambiano, corrispondendo al comportamento di importScripts().

Limitazioni attuali

Solo importazioni statiche

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

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

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

E gli altri lavoratori?

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

Chrome ed Edge supportano i moduli ES nei worker condivisi da versione 83, ma nessun altro browser offre il supporto al momento.

Nessun supporto per l'importazione delle mappe

Le mappe di importazione consentono agli ambienti di runtime di riscrivere gli specificatori dei moduli, ad esempio anteponendo l'URL di una CDN preferita da cui è possibile caricare i moduli ES.

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

Supporto browser

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

Safari ha aggiunto il supporto nella release 122 dell'anteprima tecnologica e gli sviluppatori dovrebbero aspettarsi che in futuro questa funzionalità verrà rilasciata nella versione stabile di Safari.

Esempio di codice

Questo è un esempio base di utilizzo di un modulo ES condiviso nel contesto window di un'app web, registrazione di 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 riportato sopra funzionerebbe perfettamente se tutti i browser supportassero i moduli ES nei worker di servizio, ma al momento non è così.

Per supportare i browser che non dispongono di supporto integrato, puoi eseguire lo script del service worker tramite un bundler compatibile con il modulo ES per creare un service worker che includa tutto il codice del modulo in linea e funzionerà 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().

Quando sono disponibili due versioni del service worker, una che utilizza i moduli ES e l'altra che no, devi rilevare ciò che supporta il browser corrente e registrare lo script del service worker corrispondente. Al momento, le best practice per rilevare il supporto sono in continua evoluzione, ma puoi seguire la discussione in questo problema di GitHub per ricevere suggerimenti.

_Foto di Vlado Paunovic su Unsplash_