Módulos de ES en service workers

Una alternativa moderna a importScripts().

Información general

Los módulos de ES son uno de los favoritos de los desarrolladores desde hace un tiempo. 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 ofrecen compatibilidad con módulos de ES, no todos la ofrecen en todos los lugares en los que se puede ejecutar el código. En particular, la compatibilidad para importar módulos de ES dentro del service worker de un navegador recién comienza a estar disponible de forma más amplia.

En este artículo, se detalla el estado actual de la compatibilidad del módulo ES en service workers a través de navegadores comunes, junto con algunas dificultades que debes evitar y las prácticas recomendadas para enviar código de service worker con retrocompatibilidad.

casos de uso

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

Intentar compartir código de esta manera antes de los módulos de ES implicaba el uso de formatos de módulos "universales" más antiguos, como UMD, que incluyen código estándar innecesario y código que realizó 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 cambia su contenido, de manera que coincidan con el comportamiento de importScripts().

Limitaciones actuales

Solo importaciones estáticas

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

Esta limitación es análoga a una restricción similar establecida en el 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, se podría levantar esta restricción y se podrían permitir las importaciones de módulos de ES dinámicos. Por ahora, asegúrate de usar solo la sintaxis estática dentro de un service worker.

¿Qué ocurre con los otros trabajadores?

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

Chrome y Edge tienen módulos de ES compatibles en trabajadores compartidos desde la versión 83, pero ningún otro navegador ofrece compatibilidad en este momento.

Sin compatibilidad para importar mapas

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

Si bien la versión 89 y posteriores de Chrome y Edge admiten la importación de mapas, en la actualidad no se pueden usar con service workers.

Navegadores compatibles

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

Safari agregó compatibilidad en la versión 122 de la Vista previa de la tecnología, y los desarrolladores deberían esperar ver esta funcionalidad en la versión estable de Safari en el futuro.

Ejemplo de código

Este es un ejemplo básico de cómo usar un módulo de ES compartido en el contexto window de una app web y, al mismo tiempo, registrar 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, hasta el momento de la escritura, ese no es el caso.

Puedes ejecutar la secuencia de comandos del 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 y que funcione en navegadores más antiguos a fin de alojar navegadores que no tengan compatibilidad integrada. Como alternativa, si los módulos que intentas importar ya están disponibles agrupados en formatos IIFE o UMD, puedes importarlos con importScripts().

Cuando tengas dos versiones disponibles del 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 asistencia están en constante cambio, pero puedes seguir el análisis de este problema de GitHub para obtener recomendaciones.

_Foto de Vlado Paunovic en Unsplash_