Qué es y cómo implementar la Cache API de Service Worker

Si estás en pleno desarrollo de una webapp, app progresiva o web con necesidad de características y funcionamiento sin internet, puede que éste sea tu artículo. Voy a intentar explicar de forma clara y sencilla cómo hacer una webapp offline utilizando el Service Worker Cache API.

Muchas veces nos enfrentamos ante el reto de montar una aplicación web con capacidades offline o con el objetivo de que se ejecute sin conexión a internet. Esto puede ser para que la app sea completamente funcional sin internet encolando las operaciones para posteriormente sincronizar con el servidor, o para activarse un modo «offline» de mínimos (habiendo cacheado contenidos vistos anteriormente, tipo Twitter).

Si has llegado a este artículo, probablamente estés intentando implementar una webapp capaz de ejecutarse offline y estás buscando información sobre cómo cachear recursos en la memoria del navegador. Seguramente, como me pasó a mí, hayas buscando bibliografía sobre la Cache API porque la compatibilidad con el viejo sistema WebCache se está empezando a perder.

Los ejemplos y artículos que he visto por internet no terminan de explicar con palabras sencillas y tampoco cuentan los detalles a tener en cuenta para utilizar este sistema que probablemente todavía no hayas conseguido poner en marcha. Voy a intentar resumirlo y ponértelo fácil.

Cache API es una capa del sistema Service Worker que viene ya implementada en todos los navegadores web modernos (y no tan modernos). El Service Worker Cache API es prácticamente como un proxy de red, levanta una serie de listeners en los cuales te permite ejecutar tu propio código y escoger qué deseas hacer con cada solicitud (página, recurso estático, etc.). En este caso, lo resumiremos en cachear o no cachear la petición.

Para utilizar la Caché API, es necesario instalar un Service Worker, no se puede utilizar la Cache API si no se instala el Service Worker. Es una dependencia. Por tanto, hay que almacenar todo el código de Cache API en un fichero javascript para después registrar ese fichero .JS como service worker en el navegador.

Con el Service Worker vamos a poder overridear operaciones del navegador. Por ejemplo, el método «fetch» es el que el navegador llama cada vez que va a realizar una petición HTTP y aquí podremos decidir qué hacer con cada solicitud: recuperar de caché si ya la tenemos, cachearla y servirla, o cargar de internet. En el ejemplo que os adjunto, lo que haremos es definir un array con todos los recursos a cachear, el método fetch revisará si el recurso a cargar está en la lista, y si lo está, lo cacheará.

Ejemplo de código Cache API

A continuación, os pego un código que autoexplica en comentarios qué es lo que hace en cada punto. Este sería el Service Worker a instalar en el navegador. El código que lanza la instalación del Service Worker tenemos que colocarlo en nuestro index, la raíz de nuestra aplicación.

Fichero api.js que se encuentra en la raíz del proyecto, mismo nivel que el index.

if('caches' in self) {
    debug("Navegador compatible con CacheAPI");

    var currentCache = 'gtd-v20200806';

    var files =
    [
        '.', // el index
        'style/branding/favicon.png',
        'plugins/font-awesome/webfonts/fa-solid-900.woff2',
        'plugins/font-awesome/webfonts/fa-solid-900.woff',
        'plugins/font-awesome/webfonts/fa-solid-900.ttf',
        'plugins/font-awesome/webfonts/fa-solid-900.eot',
        'plugins/font-awesome/webfonts/fa-solid-900.svg',
        'plugins/font-awesome/webfonts/fa-regular-400.woff2',
        'plugins/font-awesome/webfonts/fa-regular-400.woff',
        'plugins/font-awesome/webfonts/fa-regular-400.ttf',
        'plugins/font-awesome/webfonts/fa-regular-400.eot',
        'plugins/font-awesome/webfonts/fa-regular-400.svg',
        'plugins/localforage.js',
        'style/common.css',
        'style/estilo.css',
        'js/funciones.js',
        'js/app.js'
    ];

    self.addEventListener('install', event => {
        event.waitUntil(
            caches.open(currentCache).then(cache => {
                return cache.addAll(files).then(function() { 
                	// El skipWaiting descarta el service worker anterior e instalará el nuevo sin esperar.
			    	// Normalmente, sin esta línea, hasta que no se cierra del todo la pestaña/navegador y se abre
			    	// de nuevo, no se cogerá el nuevo service worker.
			    	// Otra opción es no hacerlo así, sino hacerlo con un botón de "ojo hay nueva versión" y 
			    	// en ese momento llamar a skipWaiting a mano
			    	self.skipWaiting();
                    debug("Caché descargada con éxito " + currentCache);
                }).catch(function(err){
                    debug("ERROR " + err);
                });
            })
        );
    });

    /**
        activate: una vez el worker está instalado, cuando arranca la app, salta el método activate
        en este caso, revisamos la versión de la caché para descargar una nueva versión si está obsoleta
    */
    self.addEventListener('activate', event => {
        debug('Ejecución evento "activate"');
        event.waitUntil(
            caches.keys().then(cacheNames => Promise.all(
                cacheNames.filter(cacheName => {
                    debug("Comprobando versión de cache " + currentCache);
                    return cacheName !== currentCache
                }).map(cacheName => caches.delete(cacheName).then(function(){
                    debug("ATENCIÓN NUEVA CACHÉ "+currentCache+"! (Caché vieja eliminada " + cacheName + ")");
                }))
            ))
        );
    });

    /**
        fetch: overridea todas las peticiones HTTP del navegador
        esta estrategia, busca si está en caché y sirve lo de caché
        en caso de que no exista, hace una petición a internet.
    */
    self.addEventListener('fetch', function(event) {
        event.respondWith(
            caches.match(event.request).then(function(response) {
                if(response === undefined){
                	// puro debug: si es un php, es dinámico. no lo mostramos en la consola.
                	if(event.request.url.indexOf(".php") === -1){
                		debug("FETCH No existe " + event.request.url);
                	}
                }else{
                    //debug("FETCH Existe " + event.request.url);
                }
                return response || fetch(event.request);
            })
        );
    });
}else{
    debug("Caché deshabilitada en el navegador, no podemos trabajar offline.");
}
function debug(mensaje){
    console.log(new Date().toISOString() + " [SW] " + mensaje);
}

Código que incorporaremos al final de nuestro index.php, antes del cierre de </body>:


Lecciones aprendidas y anotaciones sobre Service Workers y Cache API

Varias lecciones aprendidas y anotaciones importantes a destacar para que todo funcione correctamente:

El service worker sólo funciona bajo HTTPS. No se lanza, ni se ejecuta ni se instala si estamos trabajando en HTTP. Tampoco funcionará si el certificado no es válido. Aquí os explico cómo conseguir un certificado autofirmado válido.

Todos los ficheros que listemos en el addCache han de existir. Si no existen, fallará el cacheo y por tanto, el registro del service worker. Dejará de funcionar todo.

El service worker tiene que estar en la raíz de nuestra app. Sólo carga y funciona con aquello que está a partir de su localización.

Para instalar una nueva version de nuestro service worker, hay que cerrar todas las pestañas y ventanas que hayan cargado la web. Una forma de simularlo, es hacer SHIFT + R (Con CMD + SHIFT +R  en Mac). También podemos hacer que cargue todo el service worker desde el inspector de elementos, evitando el cacheo.

Cuando se cierra del todo la APP y se abre, si hay una nueva caché, entonces se actualizará. La comprobación de la versión se lanza en el método activate del listener.

Otra utilidad que podemos sacar a la Cache API es que Google premia las webs que cargan rápido en el SEO. Por tanto, si utilizamos métodos de cacheo para recursos estáticos como éste, podremos hacer que nuestra web cargue más rápido y que Google nos de mejor puntuación en los buscadores.

Referencias

Ibai

Apasionado por la tecnología, el software, las interfaces de usuario (UX UI) y los sistemas. Utilizo este canal de comunicación para transmitir de manera informal, y muchas veces "rápido", pequeñas aportaciones a la comunidad software.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *


El periodo de verificación de reCAPTCHA ha caducado. Por favor, recarga la página.

*

Artículos relacionados