Uso de Service Workers para administrar solicitudes de red

Los trabajadores de servicio son un tipo especial de trabajadores web con la capacidad de interceptar, modificar y responder a las solicitudes de red mediante la Fetch API. Los trabajadores del servicio pueden acceder a la Cache API y a los almacenes de datos asincrónicos del lado cliente, como IndexedDB, para almacenar recursos.

Los trabajadores de servicio pueden acelerar la PWA almacenando en caché los recursos localmente y también pueden hacer que su PWA sea más confiable al hacerlo independiente de la red.

La primera vez que un usuario accede a su PWA, se instala service worker. A continuación, Service Worker se ejecuta en paralelo a la aplicación y puede seguir trabajando incluso cuando la aplicación no se esté ejecutando.

Los trabajadores de servicio son responsables de interceptar, modificar y responder a las solicitudes de red. Se pueden recibir alertas cuando la aplicación intenta cargar un recurso desde el servidor o envía una solicitud para obtener datos del servidor. Cuando esto sucede, un trabajador de servicio puede decidir dejar que la solicitud vaya al servidor o interceptarla y devolver una respuesta de la memoria caché en su lugar.

Diagrama de arquitectura de alto nivel que muestra que Service Worker está entre la aplicación y la red y el almacenamiento en caché

Registro de un trabajo de servicio

De forma similar a otros trabajos web, los trabajos de servicio deben existir en un archivo independiente. Haga referencia a este archivo al registrar Service Worker, como se muestra en el código siguiente:

if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("/serviceworker.js");
}

El explorador web que ejecuta el PWA puede proporcionar distintos niveles de compatibilidad con service workers. Además, es posible que el contexto en el que se ejecuta la PWA no sea seguro. Por lo tanto, se recomienda probar la existencia del objeto antes de navigator.serviceWorker ejecutar cualquier código relacionado con Service Worker. En el código anterior, una instancia de Service Worker se registra mediante el serviceworker.js archivo que se encuentra en la raíz del sitio.

Asegúrese de colocar el archivo de Service Worker en el directorio de nivel más alto que quiera administrar. Este directorio se denomina ámbito de Service Worker. En el código anterior, el archivo se almacena en el directorio raíz de la aplicación y Service Worker administra todas las páginas que están bajo el nombre de dominio de la aplicación.

Si el archivo de Service Worker se almacena en un js directorio, el ámbito de Service Worker se limitaría al directorio y a js cualquier subdirectorio. Como procedimiento recomendado, coloque el archivo de Service Worker en la raíz de la aplicación, a menos que necesite reducir el ámbito de Service Worker.

Interceptar solicitudes

El evento principal que se usa en una instancia de Service Worker es el fetch evento . El fetch evento se ejecuta cada vez que el explorador en el que se ejecuta la aplicación intenta acceder al contenido dentro del ámbito de Service Worker.

En el código siguiente se muestra cómo agregar un agente de escucha para el fetch evento:

self.addEventListener("fetch", event => {
  console.log('WORKER: Fetching', event.request);
});

Dentro del fetch controlador, puede controlar si una solicitud va a la red, extrae de la memoria caché, etc. El enfoque que adopte probablemente variará en función del tipo de recurso que se solicite, la frecuencia con la que se actualiza y otra lógica de negocios exclusiva de la aplicación.

Estos son algunos ejemplos de lo que puede hacer dentro del fetch controlador:

  • Si está disponible, devuelva una respuesta de la memoria caché; De lo contrario, se reserva la solicitud del recurso a través de la red.
  • Capturar un recurso de la red, almacenar en caché una copia y devolver la respuesta.
  • Permitir a los usuarios especificar una preferencia para guardar datos.
  • Proporcione una imagen de marcador de posición para determinadas solicitudes de imagen.
  • Genere una respuesta directamente en service worker.

Ciclo de vida de Service Worker

El ciclo de vida de una instancia de Service Worker consta de varios pasos, y cada paso desencadena un evento. Puede agregar agentes de escucha a estos eventos para ejecutar código para realizar una acción. En la lista siguiente se presenta una vista de alto nivel del ciclo de vida y los eventos relacionados de Service Workers.

  1. Registre service worker.

  2. El explorador descarga el archivo JavaScript, instala Service Worker y desencadena el install evento. Puede usar el install evento para almacenar en caché previamente cualquier archivo importante y de larga duración (como archivos CSS, archivos JavaScript, imágenes de logotipo o páginas sin conexión) de la aplicación.

    self.addEventListener("install", event => {
        console.log("WORKER: install event in progress.");
    });
    
  3. Se activa Service Worker, lo que desencadena el activate evento. Use este evento para limpiar cachés obsoletas.

    self.addEventListener("activate", event => {
        console.log("WORKER: activate event in progress.");
    });
    
  4. Service Worker está listo para ejecutarse cuando se actualiza la página o cuando el usuario va a una página nueva en el sitio. Si desea ejecutar Service Worker sin esperar, use el self.skipWaiting() método durante el install evento, como se indica a continuación:

    self.addEventListener("install", event => {
        self.skipWaiting();
        // …
    });
    
  5. Service Worker se está ejecutando y puede escuchar eventos fetch .

Recursos anteriores a la caché

Cuando un usuario accede a la aplicación por primera vez, se instala service worker de la aplicación. Use el install evento en service worker para detectar cuándo se produce esto y almacenar en caché todos los recursos estáticos que necesita la aplicación. Almacenar en caché los recursos estáticos de la aplicación (como el código HTML, CSS y JavaScript) que necesita la página de inicio permite que la aplicación se ejecute incluso cuando el dispositivo del usuario esté sin conexión.

Para almacenar en caché los recursos de la aplicación, use el objeto global caches y el cache.addAll método , como se muestra a continuación:

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

Tenga en cuenta que después de la instalación inicial, el install evento no se vuelve a ejecutar. Para actualizar el código de Service Worker, consulte Actualización de Service Worker.

Ahora puede usar el fetch evento para devolver los recursos estáticos de la memoria caché, en lugar de cargarlos de nuevo desde la red:

self.addEventListener("fetch", event => {
  async function returnCachedResource() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Find the response that was pre-cached during the `install` event.
    const cachedResponse = await cache.match(event.request.url);

    if (cachedResponse) {
      // Return the resource.
      return cachedResponse;
    } else {
      // The resource wasn't found in the cache, so fetch it from the network.
      const fetchResponse = await fetch(event.request.url);
      // Put the response in cache.
      cache.put(event.request.url, fetchResponse.clone());
      // And return the response.
      return fetchResponse.
    }
  }

  event.respondWith(returnCachedResource());
});

Por motivos de brevedad, el ejemplo de código anterior no controla los casos en los que se produjo un error al obtener la dirección URL de la solicitud de la red.

Uso de una página sin conexión personalizada

Cuando la aplicación usa varias páginas HTML, un escenario común sin conexión es redirigir las solicitudes de navegación de página a una página de error personalizada cuando el dispositivo del usuario está sin conexión:

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
// Note the offline page in this list.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js", "/offline"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

self.addEventListener("fetch", event => {
  async function navigateOrDisplayOfflinePage() {
    try {
      // Try to load the page from the network.
      const networkResponse = await fetch(event.request);
      return networkResponse;
    } catch (error) {
      // The network call failed, the device is offline.
      const cache = await caches.open(CACHE_NAME);
      const cachedResponse = await cache.match("/offline");
      return cachedResponse;
    }
  }

  // Only call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === 'navigate') {
    event.respondWith(navigateOrDisplayOfflinePage());
  }
});

Actualización de Service Worker

Instalación de una nueva versión de Service Worker

Si realiza cambios en el código de Service Worker e implementa el nuevo archivo de Service Worker en el servidor web, los dispositivos de los usuarios comenzarán a usar gradualmente el nuevo service worker.

Cada vez que un usuario navega a una de las páginas de la aplicación, el explorador que ejecuta la aplicación comprueba si hay disponible una nueva versión de Service Worker en el servidor. El explorador detecta nuevas versiones comparando el contenido entre el trabajo de servicio existente y el nuevo service worker. Cuando se detecta un cambio, se instala el nuevo service worker (se desencadena su install evento) y, a continuación, el nuevo service worker espera a que el trabajo de servicio existente deje de usarse en el dispositivo.

En la práctica, esto significa que puede haber dos trabajadores de servicio ejecutándose al mismo tiempo, pero solo uno intercepta las solicitudes de red de la aplicación. Cuando se cierra la aplicación, el trabajo de servicio existente deja de usarse. La próxima vez que se abra la aplicación, se activará la nueva instancia de Service Worker. El activate evento se desencadena y el nuevo service worker comienza a interceptar fetch eventos.

Puede activar con fuerza el nuevo service worker en cuanto esté instalado, mediante self.skipWaiting() el controlador de eventos de install Service Worker.

Para obtener más información sobre cómo se actualizan los trabajadores de servicio, consulte Actualización de Service Worker en web.dev.

Actualizar los archivos estáticos almacenados en caché

Cuando se almacenan en caché previamente recursos estáticos como archivos de hoja de estilos CSS, como se describe en Recursos de caché previa, la aplicación solo usa las versiones almacenadas en caché de los archivos y no intenta descargar nuevas versiones.

Para asegurarse de que los usuarios obtienen los cambios más recientes en los recursos estáticos que usa la aplicación, use una convención de nomenclatura de desintegreción de caché y actualice el código de Service Worker.

La desinteción de caché significa que cada archivo estático se denomina según su versión. Esto se puede lograr de varias maneras, pero normalmente implica el uso de una herramienta de compilación que lee el contenido de un archivo y genera un identificador único basado en el contenido. Ese identificador se puede usar para asignar un nombre al archivo estático almacenado en caché.

A continuación, actualice el código de Service Worker para almacenar en caché los nuevos recursos estáticos durante install:

// The name of the new cache your app uses.
const CACHE_NAME = "my-app-cache-v2";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles-124656.css", "app-576391.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache the new static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

// Listen to the `activate` event to clear old caches.
self.addEventListener("activate", event => {
  async function deleteOldCaches() {
    // List all caches by their names.
    const names = await caches.keys();
    await Promise.all(names.map(name => {
      if (name !== CACHE_NAME) {
        // If a cache's name is the current name, delete it.
        return caches.delete(name);
      }
    }));
  }

  event.waitUntil(deleteOldCaches());
});

Compare los CACHE_NAME valores y PRE_CACHED_RESOURCES entre el fragmento de código anterior y el de los recursos de caché previa. Cuando se instala esta nueva instancia de Service Worker, se creará una nueva caché y los nuevos recursos estáticos se descargarán y almacenarán en caché. Cuando se activa Service Worker, se eliminará la memoria caché antigua. En este momento, el usuario tendrá la nueva versión de la aplicación.

A veces, realizar cambios en Service Worker puede ser complejo. Use una biblioteca como Workbox para simplificar el paso de compilación de recursos estáticos y el código de Service Worker.

Prueba de conexiones de red en la PWA

Resulta útil saber cuándo está disponible una conexión de red, con el fin de sincronizar datos o informar a los usuarios de que el estado de la red ha cambiado.

Use las siguientes opciones para probar la conectividad de red:

La navigator.onLine propiedad es un valor booleano que le permite conocer el estado actual de la red. Si el valor es true, el usuario está en línea; de lo contrario, el usuario está sin conexión.

Para obtener más información, consulte navigator.onLine en MDN.

Eventos en línea y sin conexión

Puede realizar acciones cuando cambie la conectividad de red. Puede escuchar y tomar medidas en respuesta a eventos de red. Los eventos están disponibles en los windowelementos , documenty document.body , como se muestra a continuación:

window.addEventListener("online",  function(){
    console.log("You are online!");
});
window.addEventListener("offline", function(){
    console.log("Oh no, you lost your network connection.");
});

Para obtener más información, consulte Navigator.onLine en MDN.

Otras funcionalidades

La principal responsabilidad de un trabajador de servicio es hacer que la aplicación sea más rápida y confiable en caso de una conexión de red inestable. Los trabajadores de servicio usan principalmente el evento y Cache la fetch API para hacerlo, pero pueden usar otras API para escenarios avanzados, como:

  • Sincronización en segundo plano de los datos.
  • Sincronización periódica de datos.
  • Descargas de archivos en segundo plano grandes.
  • Control y notificaciones de mensajes push.

Sincronización en segundo plano

Use la API de sincronización en segundo plano para permitir que los usuarios sigan usando la aplicación y realicen acciones incluso cuando el dispositivo del usuario esté sin conexión.

Por ejemplo, una aplicación de correo electrónico puede permitir que sus usuarios redacten y envíen mensajes en cualquier momento. El front-end de la aplicación puede intentar enviar el mensaje de inmediato y, si el dispositivo está sin conexión, Service Worker puede detectar la solicitud con error y usar la API de sincronización en segundo plano para aplazar la tarea hasta que se conecte.

Para más información, consulte Uso de la API de sincronización en segundo plano para sincronizar datos con el servidor.

Sincronización en segundo plano del período

La API de sincronización en segundo plano periódico permite a los PWA recuperar contenido nuevo periódicamente, en segundo plano, para que los usuarios puedan acceder inmediatamente al contenido cuando vuelvan a abrir la aplicación.

Al usar la API de sincronización en segundo plano periódico, las PPA no tienen que descargar contenido nuevo (por ejemplo, artículos nuevos) mientras el usuario usa la aplicación. La descarga de contenido podría ralentizar la experiencia, por lo que, en su lugar, la aplicación puede recuperar el contenido en un momento más conveniente.

Para obtener más información, consulte Uso de la API de sincronización en segundo plano periódica para obtener contenido nuevo con regularidad.

Descargas de archivos en segundo plano grandes

La API de captura en segundo plano permite a los PWA delegar completamente la descarga de grandes cantidades de datos en el motor del explorador. De este modo, la aplicación y Service Worker no tienen que ejecutarse mientras la descarga está en curso.

Esta API es útil para aplicaciones que permiten a los usuarios descargar archivos grandes (como música, películas o podcasts) para casos de uso sin conexión. La descarga se delega en el motor del explorador, que sabe cómo controlar una conexión intermitente o incluso una pérdida completa de conectividad.

Para obtener más información, consulte Uso de la API de captura en segundo plano para capturar archivos grandes cuando la aplicación o Service Worker no se estén ejecutando.

Mensajes de inserción

Los mensajes push se pueden enviar a los usuarios sin que tengan que usar la aplicación en ese momento. Un trabajador de servicio puede escuchar mensajes push enviados por el servidor incluso si la aplicación no se está ejecutando y mostrar una notificación en el centro de notificaciones del sistema operativo.

Para más información, consulte Volver a interactuar con los usuarios con mensajes push.

Depuración con DevTools

Con Microsoft Edge DevTools, puede ver si el trabajo de servicio se ha registrado correctamente y ver en qué estado de ciclo de vida se encuentra actualmente service worker. Además, puede depurar el código JavaScript en service worker.

Para más información, consulte Depuración de Service Worker.

Vea también