Módulos de ES en service workers

Una alternativa moderna a importScripts().

Información general

Hace tiempo que los módulos de ES son los favoritos de los desarrolladores. Además de otros beneficios, ofrecen la promesa de un formato de módulo universal en el que el código compartido se puede lanzar una vez y ejecutarse en navegadores y en entornos de ejecución alternativos, como Node.js. Si bien todos los navegadores modernos son compatibles con el módulo ES, no todos lo son en cualquier lugar en el que se pueda ejecutar el código. En particular, la compatibilidad con la importación de módulos ES dentro del service worker de un navegador apenas comienza a estar disponible.

En este artículo, se detalla el estado actual de la compatibilidad con módulos ES en service workers en todos los navegadores comunes, junto con algunos problemas que se deben evitar y prácticas recomendadas para enviar código de service worker retrocompatible.

Casos de uso

El caso de uso ideal para módulos ES dentro de service workers es cargar una biblioteca moderna o un código de configuración que se comparta con otros entornos de ejecución que admitan módulos ES.

Intentar compartir código de esta manera antes de los módulos ES implicaba usar formatos de módulos "universales" más antiguos, como UMD, que incluyen código estándar innecesario, y escribir código que realizaba cambios en variables expuestas globalmente.

Las secuencias de comandos importadas a través de módulos de ES pueden activar el flujo de actualización del service worker si su contenido cambia, lo que coincide con el comportamiento de importScripts().

Limitaciones actuales

Solo importaciones estáticas

Los módulos de ES se pueden importar de dos maneras: ya sea de forma estática, con la sintaxis import ... from '...', o de forma dinámica, con el método import(). Dentro de un service worker, solo se admite la sintaxis estática actualmente.

Esta limitación es análoga a una restricción similar aplicada al uso de importScripts(). Las llamadas dinámicas a importScripts() no funcionan dentro de un service worker, y todas las llamadas a importScripts(), que son síncronas de forma inherente, deben completarse antes de que este complete su fase install. Esta restricción garantiza que el navegador conozca y pueda almacenar en caché de manera implícita todo el código JavaScript necesario para la implementación de un service worker durante la instalación.

Con el tiempo, esta restricción podría quitarse y es posible permitir las importaciones dinámicas de módulos de ES. Por ahora, asegúrate de usar solo la sintaxis estática dentro de un service worker.

¿Qué ocurre con otros trabajadores?

La compatibilidad con los módulos ES en trabajadores "dedicados" (los creados con new Worker('...', {type: 'module'})) es más generalizada y es compatible con Chrome y Edge desde la versión 80, así como en las versiones recientes de Safari. Las importaciones de módulos de ES estáticas y dinámicas se admiten en trabajadores dedicados.

Chrome y Edge admiten módulos ES en trabajadores compartidos desde la versión 83, pero ningún otro navegador es compatible en este momento.

No se admite la importación de mapas.

Las importaciones de mapas permiten que los entornos de ejecución reescriban los especificadores de módulos para, por ejemplo, anteponer la URL de una CDN preferida desde la cual se pueden cargar los módulos de ES.

Si bien la versión 89 de Chrome y Edge, y las versiones posteriores, admiten la importación de mapas, por el momento, no se pueden usar con service workers.

Navegadores compatibles

Los módulos de ES en los service workers son compatibles con Chrome y Edge a partir de la versión 91.

Safari agregó compatibilidad con la versión 122 de la Vista previa de la tecnología, y los desarrolladores deberían esperar que esta función se lance en la versión estable de Safari en el futuro.

Ejemplo de código

Este es un ejemplo básico del uso de un módulo de ES compartido en el contexto window de una app web y del registro de un service worker que usa el mismo módulo de 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);
    // ...
  })());
});

Compatibilidad con versiones anteriores

El ejemplo anterior funcionaría bien si todos los navegadores fueran compatibles con los módulos ES en los service workers, pero al momento de escribir este documento, ese no es el caso.

Para adaptarte a los navegadores que no tienen compatibilidad integrada, puedes ejecutar la secuencia de comandos de tu service worker a través de un agrupador compatible con el módulo ES para crear un service worker que incluya todo el código del módulo intercalado, que funcionará en navegadores anteriores. Como alternativa, si los módulos que intentas importar ya están disponibles en formato IIFE o UMD, puedes importarlos con importScripts().

Cuando tengas disponibles dos versiones de tu service worker, una que use módulos ES y otra que no, deberás detectar lo que admite el navegador actual y registrar la secuencia de comandos del service worker correspondiente. Las prácticas recomendadas para detectar la compatibilidad están en constante cambio, pero puedes seguir el análisis de este problema de GitHub a fin de obtener recomendaciones.

_Foto de Vlado Paunovic en Unsplash_