Caché con Service Workers

Antonio Pérez27-Feb, 2020
En este artículo encontrarás:
Tags:

Unas semanas atrás os hablábamos de Angular Material y desde hace relativamente poco tiempo os hablamos de service workers, así que quizá sería buena idea dejar claro este concepto.

¿Qué son los service workers?

Un service worker es un servicio intermedio entre nuestro navegador e internet que actúa a modo de proxy y que intercepta toda la comunicación que se produce entre el dispositivo y la red. De este modo, podremos efectuar otras acciones antes de dejar pasar una petición a internet

A nivel de código se trata de un simple fichero JavaScript que gestiona esa comunicación. Su principal función es dar soporte a situaciones en las que nos encontramos sin conexión. De esta manera, podemos hacer que nuestras aplicaciones o webs funcionen cuando nos quedamos sin conexión. Imaginemos el siguiente caso: vamos usando nuestra aplicación y entramos en el garaje de nuestra casa. En condiciones normales, al quedarnos sin conexión esta aplicación dejaría de funcionar. Y ahí es donde entra el service worker, que antes de dejarnos con una desagradable pantalla con tan solo un mensaje de “no hay conexión a internet” hace que nuestra aplicación siga funcionando.

Y os preguntaréis: ¿qué clase de brujería es esta? ¿Cómo va a funcionar la app o la web sin conexión? Pues precisamente de eso va este artículo. Vamos a desmontar el truco de Houdini y destripar cómo funciona este prodigio de la tecnología.

El concepto básico es cachear el contenido del que dispongamos para que el propio service worker, en el momento en el que detecte que no tenemos conexión nos sirva el contenido que tenemos en la caché en lugar del contenido obtenido de internet.

Service workers con Angular

Las aplicaciones realizadas con Angular, al ser aplicaciones SPA (Single Page Applications) son perfectas para beneficiarse del uso de service workers.

El comportamiento de este Service Worker en Angular sigue estos 5 objetivos de su diseño:

  1. Cachear una App es como instalar una aplicación nativa. La app se cachea en bloque y todos los archivos se actualizan juntos.
  2. Una App corriendo se ejecuta con la misma versión de todos los archivos. No ocurren cosas como que de pronto se empiecen a cachear archivos de una nueva versión, que presumiblemente sean incompatibles con los que ya tenemos almacenado.
  3. Cuando un usuario refresca la aplicación, se utiliza la última versión en caché. Nuevas pestañas van a cargar la última versión de este código en caché.
  4. La actualización sucede en segundo plano, además de forma relativamente rápida una vez que se han publicado los cambios. Se va a continuar accediendo a la versión previa de la App hasta que la nueva versión esté instalada y lista.
  5. El service worker intenta contener el uso de ancho de banda. Solamente se va a almacenar una nueva versión cuando el contenido cambie.

Implementación de cacheado con service workers en Angular 9

Pero claro, todo esto es mucha teoría, algo que no nos genera nada de ansiedad a los desarrolladores, para nada. Pues bien, tras todo el hype, vamos a implementar una pequeña PWA que ilustre el uso con Angular del cacheado con Service Workers. Ya podéis dejar de hiperventilar, aquí lo tenéis paso a paso:

Vamos a crear la nueva aplicación:

ng new pwa-angular9

Una vez tenemos nuestra nueva aplicación creada vamos a añadir el soporte para el service worker que convertirá nuestra aplicación en una pwa:

ng add @angular/pwa

Ahora veremos en pantalla como se instalan los paquetes necesarios y se crearán los archivos de configuración correspondientes.

código programación angular

Además, veréis que ahora tenemos un flag en el archivo package.json, el service-worker, que nos indica la versión que hay instalada.

También vemos que se ha creado un archivo nuevo en nuestra configuración del proyecto llamado ngsw-config.json con un contenido que debería ser algo parecido a esto en este punto:

También debemos ver como el script ha añadido en nuestro app.module.ts una línea como esta (que puede variar algo dependiendo de la versión).

  1. Caché del service worker: En este punto, nuestra App ya puede cachear el contenido de la App usando el Caché Storage del navegador. Y aquí es muy importante entender cómo funciona este mecanismo de cacheado en nuestra aplicación. Abramos un momento el archivo que se ha creado, el ngsw-config.json y centrémonos en el siguiente bloque de la configuración, la clave assetGroups:

Vemos que tenemos dos elementos dentro de esta clave App y assets. Estos dos elementos nos marcan exactamente las requests que se van a almacenar en caché, por una parte, la clave app almacenará todos los archivos de la App, html, css, bundles, etc…). Por otra parte, la clave assets que almacenará, evidentemente, los assets (sobre todo imágenes).

Y llegado este momento, vamos a introducir algo de contenido para ilustrar el funcionamiento de la App, en este caso no me la voy a jugar nada y voy a crear una aplicación que tan solo puede ser un éxito y hacerse viral en internet, un título con una foto de un precioso gatete. Es cierto, tenéis toda la razón, no estoy arriesgando nada.

Vamos a sustituir el contenido del archivo app.component.html por lo siguiente, no os preocupéis por el código, todo esto os lo dejaré en un repo en GitHub, después está el enlace, de hecho el código es lo de menos, lo importante es entender todo el proceso, el código como veis es realmente muy muy básico:

<h2>Creo que he visto un lindo gatito...</h2>
<img /src/=“assets/gatito-lindo.jpg”><7pre>

Si ahora hacéis correr la App en vuestro navegador veréis la flamante App que, aunque no lo notemos está usando todas las ventajas que nos aporta el uso de service workers.

Vamos a crear un bundle de producción, ya que el service worker solo funciona en producción:

ng build --prod

Una vez ejecutado, debemos tener nuestro build disponible en la carpeta dist de la aplicación. Como ya imagino que sabréis, subir este paquete al servidor ya hace que esté disponible la app que acabamos de crear.

Vamos a servir este contenido como si estuviéramos en internet para no tener que tirar de un servidor real. Para ello vamos a usar el paquete de node http-server. Si no lo tenéis instalado solo hay que correr el siguiente comando en el terminal:

npm install -g http-server

Ok, ahora vamos a arrancar nuestro build de producción, vamos a la carpeta dist y la lanzamos con:

http-server -c-1 .

Con esto tendremos un servidor corriendo en el puerto 8080 de nuestra máquina que sirve además el bundle de producción de nuestra app.

Et voilá, aquí tenemos nuestra flamante nueva App que será un éxito corriendo, pero vamos a analizar cómo está corriendo exactamente y como está nuestro pequeño service worker, abrid las chrome web tools y, en la pestaña application vemos que nuestra app está corriendo tal y como nosotros deseamos.

Todo esto muy bien, pero ¿dónde están los archivos cacheados? No seáis impacientes, mis jóvenes padawan, en su momento todo os será revelado… Bueno, mejor os lo digo ya, un poquito más abajo en las chrome web tools, en el apartado caché storage veréis la siguiente lista de contenido cacheado para la App:

Todos estos archivos serán los que se servirán la próxima vez que que se cargue la página. De hecho, notaréis que la mejora en el rendimiento será mucho mayor, por supuesto, si se trata de una App de producción que de una App corriendo en local. Pero vamos con la prueba definitiva, ¿realmente esto va a funcionar sin conexión? Vamos a apagar el servidor y a provocar un pequeño cataclismo en internet que solo nos afecta a nosotros vamos a ver qué pasa…
Ctrl + C para parar el servidor

Efectivamente, nuestra App sigue funcionando. Ya que la tenemos cacheada con nuestro service worker. Eso sí, un detalle obvio pero no por ello menos importante es que podremos seguir viendo la App, pero no debemos olvidar que es una versión estática. Si la App tiene funcionalidad que requiera conexión con el servidor, ésta nos estará disponible. Si necesitamos dar de alta un nuevo usuario en el servidor, obviamente, si estamos offline, la funcionalidad no estará disponible. Aun así es extremadamente útil poder cachear la App en local.

Detección de cambios y recacheado de la aplicación

En este punto nos surge una duda: ¿cómo detecta el service worker que la App ha cambiado? Muy sencillo, la clave la tenemos en el archivo ngsw.json en nuestro bundle de producción de nuestra app. Cada vez que realicemos cambios en la aplicación, se generará un nuevo archivo ngsw.json, de esta forma, nuestro navegador sabrá que hay un nuevo bundle de la App en el servidor y volverá a cachear la App teniendo siempre cacheada de este modo la última versión de la aplicación.

Resumen del cacheado con service workers

Como podemos ver, cachear una App con service workers tiene muchas ventajas, desde aumento de rendimiento hasta poder seguir usando la App sin conexión al servidor, de ahí el gran auge que están teniendo las PWAs en estos últimos años, ya que podemos simular tener una App nativa, por ejemplo, en nuestro móvil sin necesidad de publicar en las distintas App stores y tan solo realizando una web App.

Como hemos visto, desde luego, es un recurso que tenemos que tener en nuestro carcaj a día de hoy si queremos mantenernos al día en tendencias de programación y que, además, posee características propias lo suficientemente interesantes que justifican investigar y pasar un poco de tiempo controlando esta técnica.

Por último, lo prometido es deuda, así que aquí os dejo el enlace al repo con el código relativo a este pequeño ejemplo que hemos puesto para crear una app que utilice cacheado en local con un service worker.

Antonio Pérez

Full Stack Developer. Desarrollo con Ruby on Rails, Angular, APIs y Bases de Datos.