El paquete workbox-window
es un conjunto de módulos diseñados para ejecutarse en el contexto window
, es decir, dentro de tus páginas web. Son un complemento de los otros paquetes
de caja de trabajo que se ejecutan en el service worker.
Estos son los objetivos y funciones clave de workbox-window
:
- Para simplificar el proceso de registro y actualizaciones de service worker, ayuda a los desarrolladores a identificar los momentos más importantes del ciclo de vida del service worker y facilita la respuesta a esos momentos.
- Ayudar a evitar que los desarrolladores cometan los errores más comunes
- Para permitir una comunicación más sencilla entre el código que se ejecuta en el service worker y el que se ejecuta en la ventana
Cómo importar y usar workbox-window
El punto de entrada principal para el paquete workbox-window
es la clase Workbox
, y puedes importarla en tu código desde nuestra CDN o con cualquiera de las herramientas populares de agrupamiento de JavaScript.
Usa nuestra CDN
La forma más fácil de importar la clase Workbox
en tu sitio es desde nuestra CDN:
<script type="module">
import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
</script>
Ten en cuenta que en este ejemplo se usa <script type="module">
y la sentencia import
para cargar la clase Workbox
. Si bien podrías pensar que necesitas transpilar este código para que funcione en navegadores más antiguos, en realidad no es necesario.
Todos los navegadores principales que admiten el service worker también admiten módulos de JavaScript nativos, por lo que no hay problema con entregar este código a cualquier navegador (los navegadores más antiguos simplemente lo ignorarán).
Carga Workbox con agrupadores de JavaScript
Si bien no se requieren herramientas para usar workbox-window
, si tu
infraestructura de desarrollo ya incluye un agrupador como
webpack o Rollup que funciona
con dependencias de npm, es posible usarlas para
cargar workbox-window
.
El primer paso es instalar
workbox-window
como una dependencia de tu aplicación:
npm install workbox-window
Luego, en uno de los archivos JavaScript de tu aplicación, haz referencia al nombre del paquete workbox-window
en el cuadro de trabajo import
:
import {Workbox} from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
Si tu agrupador admite la división de código mediante sentencias de importación dinámicas, también puedes cargar condicionalmente workbox-window
, lo que debería ayudar a reducir el tamaño del paquete principal de tu página.
Aunque workbox-window
sea bastante pequeño, no hay razón para que deba cargarse con la lógica principal de la aplicación de tu sitio, ya que los service workers, por su naturaleza, son una mejora progresiva.
if ('serviceWorker' in navigator) {
const {Workbox} = await import('workbox-window');
const wb = new Workbox('/sw.js');
wb.register();
}
Conceptos avanzados de creación de paquetes
A diferencia de los paquetes de Workbox que se ejecutan en el service worker, los archivos de compilación a los que hacen referencia los campos main
y module
de workbox-window
en package.json
se transpilan a ES5. Esto los hace compatibles con las herramientas de compilación actuales, algunas de las cuales no permiten a los desarrolladores transpilar nada de sus dependencias de node_module
.
Si el sistema de compilación sí te permite transpilar tus dependencias (o si no necesitas transpilar ningún código), es mejor importar un archivo fuente específico en lugar del paquete en sí.
Estas son las diversas maneras en que puedes importar Workbox
, junto con una explicación de lo que mostrará cada uno:
// Imports a UMD version with ES5 syntax
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');
// Imports the module version with ES5 syntax
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';
// Imports the module source file with ES2015+ syntax
import {Workbox} from 'workbox-window/Workbox.mjs';
Ejemplos
Una vez que hayas importado la clase Workbox
, podrás usarla para registrarte e interactuar con tu service worker. A continuación, se incluyen algunos ejemplos de formas en las que puedes usar Workbox
en tu aplicación:
Registra un service worker y notifica al usuario la primera vez que este esté activo
Muchas aplicaciones web usan service worker para almacenar recursos en caché previamente, de modo que su app funcione sin conexión en las cargas de páginas posteriores. En algunos casos, podría tener sentido informar al usuario que la app ahora está disponible sin conexión.
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// `event.isUpdate` will be true if another version of the service
// worker was controlling the page when this version was registered.
if (!event.isUpdate) {
console.log('Service worker activated for the first time!');
// If your service worker is configured to precache assets, those
// assets should all be available now.
}
});
// Register the service worker after event listeners have been added.
wb.register();
Notificar al usuario si un service worker se instaló, pero está atascado esperando su activación
Cuando una página controlada por un service worker existente registra un nuevo service worker, de manera predeterminada, ese service worker no se activa hasta que todos los clientes controlados por el service worker inicial se descargan por completo.
Esta es una fuente común de confusión para los desarrolladores, en especial cuando volver a cargar la página actual no hace que se active el nuevo service worker.
Para ayudar a minimizar la confusión y aclarar cuándo ocurre esta situación, la clase Workbox
proporciona un evento waiting
que puedes escuchar:
const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', event => {
console.log(
`A new service worker has installed, but it can't activate` +
`until all tabs running the current version have fully unloaded.`
);
});
// Register the service worker after event listeners have been added.
wb.register();
Notificar al usuario sobre las actualizaciones de caché del paquete workbox-broadcast-update
El paquete workbox-broadcast-update
es una excelente manera de entregar contenido de la caché (para una entrega rápida) y, al mismo tiempo, informar al usuario sobre las actualizaciones de ese contenido (con la estrategia de inactividad durante la revalidación).
Para recibir esas actualizaciones desde la ventana, puedes escuchar eventos message
de tipo CACHE_UPDATED
:
const wb = new Workbox('/sw.js');
wb.addEventListener('message', event => {
if (event.data.type === 'CACHE_UPDATED') {
const {updatedURL} = event.data.payload;
console.log(`A newer version of ${updatedURL} is available!`);
}
});
// Register the service worker after event listeners have been added.
wb.register();
Enviar al service worker una lista de las URL que se almacenarán en caché
Para algunas aplicaciones, es posible conocer todos los recursos que se deben almacenar previamente en caché al momento de la compilación, pero algunas aplicaciones entregan páginas completamente diferentes, según la URL a la que llega el usuario primero.
En el caso de las apps de la última categoría, podría tener sentido almacenar en caché solo los recursos que el usuario necesitaba para la página específica que visitó. Cuando usas el paquete workbox-routing
, puedes enviar al router una lista de URLs para almacenar en caché, y este las almacenará en caché según las reglas definidas en el router.
En este ejemplo, se envía al router una lista de las URLs que carga la página cada vez que se activa un nuevo service worker. Ten en cuenta que es correcto enviar todas las URL porque solo las URL que coincidan con una ruta definida en el service worker se almacenarán en caché:
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// Get the current page URL + all resources the page loaded.
const urlsToCache = [
location.href,
...performance.getEntriesByType('resource').map(r => r.name),
];
// Send that list of URLs to your router in the service worker.
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
// Register the service worker after event listeners have been added.
wb.register();
Momentos importantes del ciclo de vida del service worker
El ciclo de vida del service worker es complejo y puede resultar complicado comprenderlo por completo. Parte del motivo por el que es tan complejo es que debe manejar todos los casos extremos para todos los usos posibles de un service worker (por ejemplo, registrar más de un service worker, registrar diferentes service worker en marcos diferentes, registrar service worker con nombres diferentes).
Sin embargo, la mayoría de los desarrolladores que implementan el service worker no deberían tener que preocuparse por todos estos casos extremos, ya que su uso es bastante simple. La mayoría de los desarrolladores registran solo un service worker por carga de página y no cambian el nombre del archivo del service worker que implementan en su servidor.
La clase Workbox
adopta esta vista más simple para el ciclo de vida del service worker, ya que divide todos los registros de service worker en dos categorías: el service worker propio de la instancia y uno externo:
- Service worker registrado: es un service worker que comenzó a instalarse como resultado de que la instancia
Workbox
llama aregister()
o al service worker que ya está activo si la llamada aregister()
no activó un eventoupdatefound
en el registro. - Service worker externo: es un service worker que comenzó a instalarse independientemente de la instancia
Workbox
que llama aregister()
. Esto suele suceder cuando un usuario tiene una versión nueva de tu sitio abierta en otra pestaña. Cuando se origina un evento en un service worker externo, la propiedadisExternal
del evento se establece entrue
.
Con estos dos tipos de service worker en mente, a continuación se muestra un desglose de todos los momentos importantes del ciclo de vida de los service worker, junto con las recomendaciones para desarrolladores sobre cómo manejarlos:
La primera vez que se instala un service worker
Es probable que te convenga tratar la primera vez que se instala un service worker de manera diferente a como tratar todas las actualizaciones futuras.
En workbox-window
, puedes diferenciar entre la primera instalación de la versión y las actualizaciones futuras si verificas la propiedad isUpdate
en cualquiera de los siguientes eventos. Para la primera instalación, isUpdate
será false
.
const wb = new Workbox('/sw.js');
wb.addEventListener('installed', event => {
if (!event.isUpdate) {
// First-installed code goes here...
}
});
wb.register();
Cuando se encuentra una versión actualizada del service worker
Cuando se comience a instalar un service worker nuevo, pero una versión existente controle la página actualmente, la propiedad isUpdate
de todos los eventos siguientes será true
.
La forma en que reaccionas en esta situación suele ser diferente de la primera instalación, ya que debes administrar cuándo y cómo el usuario recibirá esta actualización.
Cuando se encuentra una versión inesperada del service worker
A veces, los usuarios mantienen tu sitio abierto en una pestaña en segundo plano durante mucho tiempo. Incluso, es posible que abran una pestaña nueva y naveguen a tu sitio sin darse cuenta de que ya lo tienen abierto en una pestaña en segundo plano. En esos casos, es posible tener dos versiones de tu sitio ejecutándose al mismo tiempo, y eso puede generar algunos problemas interesantes para ti como desarrollador.
Supongamos que tienes la pestaña A que ejecuta la v1 de tu sitio y la pestaña B que ejecuta la v2. Cuando se cargue la pestaña B, la controlará la versión del service worker que se envió con la versión 1, pero la página que muestre el servidor (si se usa una estrategia de almacenamiento en caché centrada en la red para tus solicitudes de navegación) contendrá todos los recursos de la versión 2.
Sin embargo, por lo general, esto no es un problema para la pestaña B, ya que cuando escribiste el código de la versión 2, sabías cómo funcionaba el código de la versión 1. Sin embargo, podría ser un problema para la pestaña A, ya que el código de la versión 1 no podría haber predicho los cambios que podría introducir el código de la versión 2.
Para ayudar a controlar estas situaciones, workbox-window
también despacha eventos de ciclo de vida cuando detecta una actualización de un service worker "externo", donde "externo" solo se refiere a cualquier versión que no sea la versión que registra la instancia Workbox
actual.
A partir de Workbox v6 y versiones posteriores, estos eventos son equivalentes a los eventos documentados anteriormente, con la adición de una propiedad isExternal: true
establecida en cada objeto de
evento. Si tu aplicación web necesita implementar una lógica específica para controlar un service worker "externo", puedes verificar esa propiedad en tus controladores de eventos.
Cómo evitar errores comunes
Una de las funciones más útiles que proporciona Workbox es el registro de desarrolladores. En especial, workbox-window
.
Sabemos que desarrollar con un service worker puede ser confuso y, cuando sucede algo contrario a lo que esperas, puede ser difícil saber el motivo.
Por ejemplo, cuando realizas un cambio en tu service worker y vuelves a cargar la página, es posible que no veas ese cambio en el navegador. El motivo más probable es que tu service worker todavía esté esperando para activarse.
Sin embargo, cuando registres un service worker con la clase Workbox
, se te informará sobre todos los cambios de estado del ciclo de vida en la consola para desarrolladores, lo que debería ayudarte a depurar el motivo por el cual las cosas no funcionan como esperas.
Además, un error común que cometen los desarrolladores cuando usan un service worker por primera vez es registrar un service worker en el alcance incorrecto.
Para evitar que esto suceda, la clase Workbox
te advertirá si la página que registra el service worker no está dentro de su alcance. También mostrará una advertencia en los casos en los que tu service worker esté activo, pero aún no controle la página:
Comunicación entre la ventana para el service worker
El uso más avanzado del service worker implica una gran cantidad de mensajes entre el service worker y la ventana. La clase Workbox
también ayuda con esto proporcionando un método messageSW()
, que postMessage()
el service worker registrado de la instancia y espera una respuesta.
Si bien puedes enviar datos al service worker en cualquier formato, el formato que comparten todos los paquetes de Workbox es un objeto con tres propiedades (las dos últimas son opcionales):
Los mensajes enviados a través del método messageSW()
usan MessageChannel
para que el receptor pueda responderlos. Para responder un mensaje, puedes llamar a event.ports[0].postMessage(response)
en tu objeto de escucha de eventos de mensaje. El método messageSW()
muestra una promesa que se resolverá a cualquier response
con la que respondas.
Este es un ejemplo de cómo enviar mensajes desde la ventana al service worker y obtener una respuesta. El primer bloque de código es el objeto de escucha de mensajes en el service worker, y el segundo usa la clase Workbox
para enviar el mensaje y esperar la respuesta:
Código en sw.js:
const SW_VERSION = '1.0.0';
addEventListener('message', event => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Código de main.js (se ejecuta en la ventana):
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Administra las incompatibilidades de versiones
En el ejemplo anterior, se muestra cómo implementar la verificación de la versión del service worker desde la ventana. Este ejemplo se usa porque cuando envías mensajes entre la ventana y el service worker, es fundamental que sepas que es posible que tu service worker no ejecute la misma versión del sitio que ejecuta el código de tu página, y que la solución para resolver este problema varía según la publicación de tus páginas priorizando la red o la caché.
Prioridad de red
Cuando se entregue primero la red de páginas, los usuarios siempre obtendrán la versión más reciente de tu HTML del servidor. Sin embargo, la primera vez que un usuario visite tu sitio (después de implementar una actualización), el código HTML que obtendrá será para la versión más reciente, pero el service worker que se ejecute en su navegador será una versión instalada previamente (posiblemente muchas versiones anteriores).
Es importante que comprendas esta posibilidad, ya que, si el código JavaScript cargado por la versión actual de tu página envía un mensaje a una versión anterior de tu service worker, es posible que esa versión no sepa cómo responder (o que responda con un formato incompatible).
Por lo tanto, es una buena idea crear siempre versiones de tu service worker y comprobar si hay versiones compatibles antes de realizar cualquier trabajo crítico.
Por ejemplo, en el código anterior, si la versión del service worker que muestra esa llamada a messageSW()
es anterior a la versión esperada, sería aconsejable esperar hasta que se encuentre una actualización (lo cual debería suceder cuando se llama a register()
). En ese momento, puedes notificar al usuario o realizar una actualización, o bien puedes omitir la fase de espera de forma manual para activar el nuevo service worker de inmediato.
Primero en caché
A diferencia de lo que ocurre cuando entregas páginas primero en la red, cuando las entregas están primero en caché, sabes que inicialmente tu página será la misma versión que el service worker (porque es lo que lo publicó). Por lo tanto, es seguro usar messageSW()
de inmediato.
Sin embargo, si se encuentra una versión actualizada de tu service worker y se activa cuando tu página llama a register()
(es decir, si omites la fase de espera de manera intencional), es posible que ya no sea seguro enviarle mensajes.
Una estrategia para administrar esta posibilidad es usar un esquema de control de versiones que te permita diferenciar entre las actualizaciones rotundas y las no rotundas, y, en el caso de una actualización rotunda, sabrías que no es seguro enviar un mensaje al service worker. En cambio, puedes advertir al usuario que está ejecutando una versión anterior de la página y sugerir que vuelva a cargar la página para obtener la actualización.
Omitir asistente de espera
Una convención de uso común para la mensajería de ventana al service worker es enviar un mensaje {type: 'SKIP_WAITING'}
a fin de indicar a un service worker instalado que omita la fase de espera y se active.
A partir de Workbox v6, se puede usar el método messageSkipWaiting()
para enviar un
mensaje {type: 'SKIP_WAITING'}
al service worker en espera asociado con el
registro actual del service worker. No realizará ninguna acción silenciosa si no hay un service worker en espera.
Tipos
Workbox
Es una clase que ayuda a controlar el registro y las actualizaciones de un service worker, así como a reaccionar ante sus eventos de ciclo de vida.
Propiedades
-
constructor
void
Crea una instancia de Workbox nueva con una URL de secuencia de comandos y opciones de service worker. La URL y las opciones de la secuencia de comandos son las mismas que las que se usan cuando se llama a navigator.serviceWorker.register(scriptURL, options).
La función
constructor
se ve de la siguiente manera:(scriptURL: string | TrustedScriptURL, registerOptions?: object) => {...}
-
scriptURL
cadena | URL de TrustedScript
La secuencia de comandos del service worker asociada a esta instancia. Se admite el uso de
TrustedScriptURL
. -
registerOptions
objeto opcional
-
resultados
-
-
activo
Promise<ServiceWorker>
-
controlando
Promise<ServiceWorker>
-
getSW
void
Se resuelve con una referencia a un service worker que coincide con la URL de la secuencia de comandos de esta instancia en cuanto está disponible.
Si, al momento del registro, ya hay un service worker activo o en espera con una URL de secuencia de comandos coincidente, se usará este último (el service worker en espera tendrá prioridad sobre el activo si ambos coinciden, ya que el service worker en espera se registró recientemente). Si no hay un service worker activo o en espera correspondiente al momento del registro, la promesa no se resolverá hasta que se encuentre una actualización y comience a instalarse, momento en el cual se usa el service worker de instalación.
La función
getSW
se ve de la siguiente manera:() => {...}
-
resultados
Promise<ServiceWorker>
-
-
messageSW
void
Envía el objeto de datos pasado al service worker registrado por esta instancia (a través de
workbox-window.Workbox#getSW
) y se resuelve con una respuesta (si existe).Para configurar una respuesta en un controlador de mensajes en el service worker, se debe llamar a
event.ports[0].postMessage(...)
, lo que resolverá la promesa que muestramessageSW()
. Si no se establece una respuesta, la promesa nunca se resolverá.La función
messageSW
se ve de la siguiente manera:(data: object) => {...}
-
datos
objeto
Un objeto para enviar al service worker
-
resultados
Promesa<cualquiera>
-
-
messageSkipWaiting
void
Envía un mensaje
{type: 'SKIP_WAITING'}
al service worker que, en ese momento, se encuentra en el estadowaiting
asociado con el registro actual.Si no hay un registro actual o ningún service worker es
waiting
, la llamada a este elemento no tendrá ningún efecto.La función
messageSkipWaiting
se ve de la siguiente manera:() => {...}
-
register
void
Registra un service worker para la URL de la secuencia de comandos de estas instancias y las opciones de service worker. De forma predeterminada, este método retrasa el registro hasta después de que se carga la ventana.
La función
register
se ve de la siguiente manera:(options?: object) => {...}
-
Opciones
objeto opcional
-
inmediato
booleano opcional
-
-
resultados
Promise<ServiceWorkerRegistration>
-
-
update
void
Busca actualizaciones del service worker registrado.
La función
update
se ve de la siguiente manera:() => {...}
-
resultados
Promise<void>
-
WorkboxEventMap
Propiedades
-
Activado
-
activando
-
controlando
-
Instalada
-
instala
-
mensaje
-
redundante
-
esperando
WorkboxLifecycleEvent
Propiedades
-
isExternal
booleano opcional
-
isUpdate
booleano opcional
-
originalEvent
Evento opcional
-
sw
ServiceWorker opcional
-
destino
WorkboxEventTarget opcional
-
Tipo
typeOperator
WorkboxLifecycleEventMap
Propiedades
-
Activado
-
activando
-
controlando
-
Instalada
-
instala
-
redundante
-
esperando
WorkboxLifecycleWaitingEvent
Propiedades
-
isExternal
booleano opcional
-
isUpdate
booleano opcional
-
originalEvent
Evento opcional
-
sw
ServiceWorker opcional
-
destino
WorkboxEventTarget opcional
-
Tipo
typeOperator
-
wasWaitingBeforeRegister
booleano opcional
WorkboxMessageEvent
Propiedades
-
datos
cualquiera
-
isExternal
booleano opcional
-
originalEvent
Evento
-
ports
typeOperator
-
sw
ServiceWorker opcional
-
destino
WorkboxEventTarget opcional
-
Tipo
Métodos
messageSW()
workbox-window.messageSW(
sw: ServiceWorker,
data: object,
)
Envía un objeto de datos a un service worker a través de postMessage
y se resuelve con una respuesta (si existe).
Para configurar una respuesta en un controlador de mensajes en el service worker, se debe llamar a event.ports[0].postMessage(...)
, lo que resolverá la promesa que muestra messageSW()
. Si no se establece una respuesta, la promesa no se resolverá.
Parámetros
-
sw
ServiceWorker
El service worker al que se enviará el mensaje
-
datos
objeto
Un objeto para enviar al service worker.
Devuelve
-
Promesa<cualquiera>