Módulos de ES en service workers

Una alternativa moderna a importScripts().

Segundo plano

Los módulos de ES han sido los favoritos de los desarrolladores desde hace tiempo. Además de una serie 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 ejecutar en navegadores y en entornos de ejecución alternativos, como Node.js. Si bien todos los navegadores modernos ofrecen cierta compatibilidad con los módulos de ES, no todos ofrecen compatibilidad en todas partes en las que se puede ejecutar el código. Específicamente, la compatibilidad para importar módulos de ES dentro del service worker de un navegador está empezando a estar disponible para más usuarios.

En este artículo, se detalla el estado actual de la compatibilidad con módulos ES en los trabajadores del servicio en navegadores comunes, junto con algunos errores que se deben evitar y prácticas recomendadas para enviar código de trabajador del servicio retrocompatible.

Casos de uso

El caso de uso ideal para los módulos de ES dentro de los trabajadores del servicio 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 de ES.

Intentar compartir código de esta manera antes de los módulos de ES implicaba usar formatos de módulos “universales” más antiguos, como UMD, que incluyen texto de referencia innecesario y escribir código que realizaba cambios en variables expuestas de forma global.

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

Limitaciones actuales

Solo importaciones estáticas

Los módulos de ES se pueden importar de una de las siguientes maneras: estática, con la sintaxis import ... from '...', o dinámica, con el método import(). Dentro de un trabajador de servicio, por el momento, solo se admite la sintaxis estática.

Esta limitación es similar a una restricción similar que se aplica al uso de importScripts(). Las llamadas dinámicas a importScripts() no funcionan dentro de un trabajador de servicio, y todas las llamadas a importScripts(), que son intrínsecamente síncronas, deben completarse antes de que el trabajador de servicio complete su fase install. Esta restricción garantiza que el navegador conozca todo el código JavaScript necesario para la implementación de un trabajador de servicio y que pueda almacenarlo en caché de forma implícita durante la instalación.

Con el tiempo, es posible que se quite esta restricción y que se permitan las importaciones de módulos dinámicos de ES. Por ahora, asegúrate de usar solo la sintaxis estática dentro de un trabajador de servicio.

¿Qué ocurre con los demás trabajadores?

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

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

No se admiten mapas de importación.

Los mapas de importación 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 que se pueden cargar los módulos de ES.

Si bien Chrome y Edge versión 89 y versiones posteriores admiten mapas de importación, actualmente no se pueden usar con trabajadores de servicio.

Navegadores compatibles

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

Safari agregó compatibilidad en la versión 122 de la Versión preliminar de tecnología, y los desarrolladores deberían esperar ver esta funcionalidad lanzada 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 ES compartido en el contexto window de una app web y, al mismo tiempo, registra un trabajador de servicio que usa el mismo módulo 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 admitieran módulos ES en los trabajadores de servicio, pero, en el momento de escribir este artículo, no es así.

Para admitir navegadores que no tienen compatibilidad integrada, puedes ejecutar tu secuencia de comandos de trabajador de servicio a través de un agrupador compatible con el módulo ES para crear un trabajador de servicio que incluya todo el código del módulo intercalado y que funcione en navegadores más antiguos. Como alternativa, si los módulos que intentas importar ya están disponibles en paquetes en los formatos IIFE o UMD, puedes importarlos con importScripts().

Una vez que tengas dos versiones de tu trabajador de servicio disponibles (una que use módulos de ES y la otra que no), deberás detectar lo que admite el navegador actual y registrar la secuencia de comandos del trabajador de servicio correspondiente. Las prácticas recomendadas para detectar la compatibilidad están en constante cambio, pero puedes seguir el debate en este problema de GitHub para obtener recomendaciones.

_Foto de Vlado Paunovic en Unsplash_