La vida de un service worker

Es difícil saber qué están haciendo los service workers sin comprender su ciclo de vida. Su funcionamiento interno parecerá opaco, incluso arbitrario. Es útil recordar que, como cualquier otra API de navegador, los comportamientos del service worker están bien definidos, especificar y hacer posibles aplicaciones sin conexión, al mismo tiempo que facilita las actualizaciones sin interrumpir la experiencia del usuario.

Antes de empezar a trabajar en Workbox, Es importante comprender el ciclo de vida del service worker para saber qué tiene sentido usar Workbox.

Definición de términos

Antes de entrar en el ciclo de vida del service worker, vale la pena definir algunos términos sobre cómo funciona ese ciclo de vida.

Control y alcance

La idea de control es crucial para comprender cómo operan los service workers. Una página que se describe como controlada por un service worker es aquella que permite que un service worker intercepte solicitudes de red en su nombre. El service worker está presente y puede realizar tareas para la página dentro de un alcance determinado.

Alcance

El alcance de un service worker se determina por su ubicación en el servidor web. Si un service worker se ejecuta en una página ubicada en /subdir/index.html y en /subdir/sw.js, el permiso del service worker es /subdir/. Para ver el concepto de alcance en acción, consulta este ejemplo:

  1. Navegar a https://service-worker-scope-viewer.glitch.me/subdir/index.html. Aparecerá un mensaje que indica que ningún service worker controla la página. Sin embargo, esa página registra un service worker de https://service-worker-scope-viewer.glitch.me/subdir/sw.js.
  2. Vuelve a cargar la página. Debido a que el service worker se registró y ahora está activo, controla la página. Un formulario que contiene el alcance del service worker. el estado actual y su URL será visible. Nota: Tener que volver a cargar la página no tiene nada que ver con el alcance, sino el ciclo de vida del service worker, que explicaremos más adelante.
  3. Ahora ve a https://service-worker-scope-viewer.glitch.me/index.html. Aunque se registró un service worker en este origen, sigue apareciendo un mensaje que dice que no hay un service worker actual. Esto se debe a que esta página no está dentro del alcance del service worker registrado.

El permiso limita las páginas que controla el service worker. En este ejemplo, eso significa que el service worker que se carga desde /subdir/sw.js solo puede controlar páginas ubicadas en /subdir/ o su subárbol.

Lo anterior es cómo funciona el alcance de forma predeterminada pero el permiso máximo permitido se puede anular estableciendo Encabezado de respuesta Service-Worker-Allowed, así como pasar un Opción scope al método register.

A menos que haya un buen motivo para limitar el alcance del service worker a un subconjunto de un origen, cargar un service worker desde el directorio raíz del servidor web para que su alcance sea lo más amplio posible y no te preocupes por el encabezado Service-Worker-Allowed. De esa manera, es mucho más sencillo para todos.

Cliente

Cuando se dice que un service worker controla una página, en realidad controla un cliente. Un cliente es cualquier página abierta cuya URL esté dentro del alcance de ese service worker. Específicamente, estas son instancias de un WindowClient.

Ciclo de vida de un service worker nuevo

Para que un service worker controle una página, debe implementarse, por así decirlo. Comencemos con lo que sucede cuando se implementa un service worker nuevo para un sitio web sin service worker activo.

Registro

El registro es el paso inicial del ciclo de vida del service worker:

<!-- In index.html, for example: -->
<script>
  // Don't register the service worker
  // until the page has fully loaded
  window.addEventListener('load', () => {
    // Is service worker available?
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js').then(() => {
        console.log('Service worker registered!');
      }).catch((error) => {
        console.warn('Error registering service worker:');
        console.warn(error);
      });
    }
  });
</script>

Este código se ejecuta en el subproceso principal y hace lo siguiente:

  1. Como la primera visita del usuario a un sitio web se produce sin un service worker registrado, Espera hasta que la página se cargue por completo antes de registrar una. Esto evita la contención del ancho de banda si el service worker almacena previamente en caché.
  2. Si bien el service worker es compatible, una verificación rápida ayuda a evitar errores en navegadores que no son compatibles.
  3. Cuando la página esté completamente cargada, y si el service worker es compatible, registra /sw.js.

Estos son algunos puntos clave que debes comprender:

  • Los service workers son Solo disponible a través de HTTPS o localhost.
  • Si el contenido de un service worker tiene errores de sintaxis, falla el registro y se descarta el service worker.
  • Recordatorio: Los service workers operan dentro de un permiso. Aquí, el alcance es el origen completo, como se cargó desde el directorio raíz.
  • Cuando comienza el registro, el estado del service worker se establece en 'installing'.

Una vez finalizado el registro, comenzará la instalación.

Instalación

Un service worker activa su install después del registro. Solo se llama a install una vez por service worker y no se volverá a activar hasta que se actualice. Se puede registrar una devolución de llamada para el evento install en el alcance del trabajador con addEventListener:

// /sw.js
self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v1';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v1'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.bc7b80b7.css',
      '/css/home.fe5d0b23.css',
      '/js/home.d3cc4ba4.js',
      '/js/jquery.43ca4933.js'
    ]);
  }));
});

Esto crea una nueva instancia de Cache y almacena previamente en caché los recursos. Tendremos muchas oportunidades para hablar sobre el almacenamiento previo en caché más adelante así que centrémonos en el rol de event.waitUntil event.waitUntil acepta una promesa, y espera hasta que se resuelva. En este ejemplo, la promesa realiza dos acciones asíncronas:

  1. Crea una nueva instancia de Cache llamada 'MyFancyCache_v1'.
  2. Después de crear la caché, un array de URLs de recursos se almacena en caché previamente mediante su módulo Método addAll.

La instalación falla si las promesas que se pasaron a event.waitUntil no rechazada. Si esto sucede, se descarta el service worker.

Si las promesas se resuelven, la instalación se realiza correctamente y el estado del service worker cambiará a 'installed' y, luego, se activará.

Activación

Si el registro y la instalación se completan correctamente, se activa el service worker, y su estado se vuelve 'activating' El trabajo puede realizarse durante la activación en la interfaz del activate evento. Una tarea típica en este evento es reducir cachés antiguas, pero para un service worker nuevo, no es relevante por el momento y se ampliará cuando hablemos de las actualizaciones de los service workers.

En el caso de los service workers nuevos, activate se activa inmediatamente después de que install se ejecuta de forma correcta. Una vez finalizada la activación, el estado del service worker se convierte en 'activated'. Observa que, de forma predeterminada, El nuevo service worker no comenzará a controlar la página hasta que se actualice la página o la siguiente navegación.

Cómo administrar las actualizaciones del service worker

Una vez implementado el primer service worker, es probable que tengas que actualizarla más tarde. Por ejemplo, es posible que se requiera una actualización si se producen cambios en la lógica del manejo de solicitudes o de almacenamiento previo en caché.

Cuándo se realizan las actualizaciones

Los navegadores buscarán actualizaciones para un service worker en los siguientes casos:

  • El usuario navega a una página dentro del alcance del service worker.
  • navigator.serviceWorker.register() se llama con una URL diferente del service worker actualmente instalado, pero no cambies la URL de un service worker.
  • navigator.serviceWorker.register() se llama con la misma URL que el service worker instalado, pero con un alcance diferente. Nuevamente, para evitar esto, mantén el alcance en la raíz de un origen, si es posible.
  • Cuando los eventos, como 'push' o 'sync' se activaron en las últimas 24 horas, pero todavía no te preocupes por estos eventos.

Cómo se realizan las actualizaciones

Es importante saber cuándo el navegador actualiza un service worker, sino también el "cómo". Si la URL o el alcance de un service worker no cambian, un service worker actualmente instalado solo se actualiza a una nueva versión si ha cambiado su contenido.

Los navegadores detectan los cambios de dos formas:

  • Cualquier cambio byte por byte a secuencias de comandos que solicita importScripts, si corresponde
  • Cualquier cambio en el código de nivel superior del service worker lo que afecta la huella digital que el navegador generó.

Aquí, el navegador realiza muchos trabajos pesados. Para garantizar que el navegador cuente con todo lo necesario para detectar de manera confiable los cambios en el contenido de un service worker, no le digas a la caché HTTP que la conserve y no cambies el nombre del archivo. El navegador realiza comprobaciones de actualizaciones automáticamente cuando se navega a una página nueva dentro del alcance de un service worker.

Cómo activar manualmente las verificaciones de actualizaciones

Con respecto a las actualizaciones, la lógica de registro generalmente no debería cambiar. Sin embargo, una excepción podría ser si las sesiones en un sitio web son de larga duración. Esto puede suceder en aplicaciones de una sola página en las que solicitudes de navegación son poco frecuentes, ya que la aplicación generalmente encuentra una solicitud de navegación al comienzo de su ciclo de vida. En estas situaciones, se puede activar una actualización manual en el subproceso principal:

navigator.serviceWorker.ready.then((registration) => {
  registration.update();
});

Para los sitios web tradicionales, o en los casos en los que las sesiones de usuario no son de larga duración, probablemente no sea necesario activar las actualizaciones manuales.

Instalación

Cuando se usa un agrupador para generar recursos estáticos, esos recursos contendrán hashes en su nombre, como framework.3defa9d2.js. Supongamos que algunos de esos elementos se almacenan previamente en caché para poder acceder a ellos sin conexión más adelante. Esto requeriría una actualización del service worker para almacenar previamente en caché los recursos actualizados:

self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v2';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v2'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.ced4aef2.css',
      '/css/home.cbe409ad.css',
      '/js/home.109defa4.js',
      '/js/jquery.38caf32d.js'
    ]);
  }));
});

Hay dos cosas que difieren del primer ejemplo del evento install del anterior:

  1. Se crea una nueva instancia de Cache con una clave de 'MyFancyCacheName_v2'.
  2. Cambiaron los nombres de los elementos previamente almacenados en caché.

Ten en cuenta que un service worker actualizado se instala junto con el anterior. Esto significa que el service worker antiguo todavía tiene el control de las páginas abiertas y, luego de la instalación, el nuevo entra en estado de espera hasta que se activa.

De forma predeterminada, se activará un nuevo service worker cuando el anterior no controle ningún cliente. Esto ocurre cuando se cierran todas las pestañas abiertas del sitio web correspondiente.

Activación

Cuando se instala un service worker actualizado y finaliza la fase de espera, cuando se activa, y se descarta el service worker antiguo. Una tarea común que se realiza en el evento activate de un service worker actualizado es reducir las cachés antiguas. Obtén las claves de todas las instancias Cache abiertas con las claves para quitar cachés antiguas caches.keys y borrar cachés que no estén en una lista de permisos definida con caches.delete

self.addEventListener('activate', (event) => {
  // Specify allowed cache keys
  const cacheAllowList = ['MyFancyCacheName_v2'];

  // Get all the currently active `Cache` instances.
  event.waitUntil(caches.keys().then((keys) => {
    // Delete all caches that aren't in the allow list:
    return Promise.all(keys.map((key) => {
      if (!cacheAllowList.includes(key)) {
        return caches.delete(key);
      }
    }));
  }));
});

Las cachés antiguas no se ordenan solas. Debemos hacerlo nosotros mismos o arriesgarnos a superar cuotas de almacenamiento. Como 'MyFancyCacheName_v1' del primer service worker está desactualizado, la lista de elementos permitidos de caché se actualiza para especificar 'MyFancyCacheName_v2' que borra las cachés con un nombre diferente.

El evento activate finalizará después de que se quite la caché anterior. En este punto, el nuevo service worker tomará el control de la página por fin reemplazaremos el anterior.

El ciclo de vida continúa

Si Workbox se usa para manejar la implementación y las actualizaciones de service worker o la API de Service Worker se usa directamente, vale la pena comprender el ciclo de vida del service worker. Si entiendes esto, los comportamientos de los service workers deberían parecer más lógicos que misteriosos.

Para quienes estén interesados en profundizar en este tema, vale la pena consultar este artículo de Jake Archibald. Hay toneladas de matices en cómo se desarrolla todo el baile en torno al ciclo de vida del servicio pero es conocido, y ese conocimiento llegará lejos cuando uses Workbox.