Una guía para principiantes sobre el uso de la caché de aplicaciones

Introducción

Es cada vez más importante que las aplicaciones basadas en la Web sean accesibles sin conexión. Sí, todos los navegadores pueden almacenar en caché páginas y recursos durante largos períodos si se les solicita que lo hagan, pero el navegador puede quitar elementos individuales de la caché en cualquier momento para dejar espacio para otras cosas. HTML5 aborda algunas de las molestias de estar sin conexión con la interfaz ApplicationCache. El uso de la interfaz de caché le brinda a tu aplicación tres ventajas:

  1. Navegación sin conexión: Los usuarios pueden navegar por todo tu sitio cuando no tienen conexión.
  2. Velocidad: Los recursos provienen directamente del disco, sin pasar por la red.
  3. Resiliencia: Si tu sitio se cae por "mantenimiento" (es decir, si alguien lo daña todo por accidente), tus usuarios obtendrán la experiencia sin conexión.

La caché de aplicaciones (o AppCache) permite que un desarrollador especifique qué archivos debe almacenar en caché el navegador y poner a disposición de los usuarios sin conexión. Tu app se cargará y funcionará correctamente, incluso si el usuario presiona el botón de actualización mientras está sin conexión.

El archivo de manifiesto de caché

El archivo de manifiesto de caché es un archivo de texto simple que enumera los recursos que el navegador debe almacenar en caché para el acceso sin conexión.

Cómo hacer referencia a un archivo de manifiesto

Para habilitar la caché de la aplicación para una app, incluye el atributo manifiesto en la etiqueta html del documento:

<html manifest="example.appcache">
  ...
</html>

El atributo manifest se debe incluir en cada página de la aplicación web que desees almacenar en caché. El navegador no almacena en caché una página si no contiene el atributo manifest (a menos que se incluya de forma explícita en el archivo de manifiesto). Esto significa que cualquier página a la que navegue el usuario que incluya un manifest se agregará de manera implícita a la caché de la aplicación. Por lo tanto, no es necesario que enumeres todas las páginas de tu manifiesto. Si una página apunta a un manifiesto, no hay forma de evitar que se almacene en caché.

Para ver las URLs que controla la caché de la aplicación, visita about://://appcache-internals/ en Chrome. Desde aquí, puedes borrar las cachés y ver las entradas. Existen herramientas para desarrolladores similares en Firefox.

El atributo manifest puede apuntar a una URL absoluta o a una ruta de acceso relativa, pero una URL absoluta debe estar en el mismo origen que la aplicación web. Un archivo de manifiesto puede tener cualquier extensión de archivo, pero debe entregarse con el tipo MIME correcto (consulta a continuación).

<html manifest="http://www.example.com/example.mf">
  ...
</html>

Se debe entregar un archivo de manifiesto con el tipo de MIME text/cache-manifest. Es posible que debas agregar un tipo de archivo personalizado a tu servidor web o a la configuración de .htaccess.

Por ejemplo, para entregar este tipo mime en Apache, agrega esta línea a tu archivo de configuración:

AddType text/cache-manifest .appcache

O bien, en tu archivo app.yaml en Google App Engine:

- url: /mystaticdir/(.*\.appcache)
  static_files: mystaticdir/\1
  mime_type: text/cache-manifest
  upload: mystaticdir/(.*\.appcache)

Este requisito se eliminó de la especificación hace tiempo y las versiones más recientes de Chrome, Safari y Firefox ya no lo requieren, pero necesitarás el tipo mime para que funcione en navegadores más antiguos y en IE11.

Estructura de un archivo de manifiesto

El manifiesto es un archivo independiente al que vinculas a través del atributo manifiesto en el elemento html. Un manifiesto simple se ve de la siguiente manera:

CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js
http://cdn.example.com/scripts/main.js

En este ejemplo, se almacenarán en caché cuatro archivos en la página que especifica este archivo de manifiesto.

Debes tener en cuenta los siguientes aspectos:

  • La cadena CACHE MANIFEST es la primera línea y es obligatoria.
  • Los archivos pueden ser de otro dominio
  • Algunos navegadores imponen restricciones a la cantidad de cuota de almacenamiento disponible para tu app. En Chrome, por ejemplo, AppCache usa un grupo compartido de almacenamiento TEMPORAL que otras APIs sin conexión pueden compartir. Si escribes una app para Chrome Web Store, usar unlimitedStorage quita esa restricción.
  • Si el manifiesto muestra un error 404 o 410, se borra la caché.
  • Si no se puede descargar el manifiesto o un recurso especificado en él, falla todo el proceso de actualización de la caché. El navegador seguirá usando la caché de la aplicación anterior en caso de falla.

Veamos un ejemplo más complejo:

CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
*

# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg

Las líneas que comienzan con una "#" son líneas de comentarios, pero también pueden tener otro propósito. La caché de una aplicación solo se actualiza cuando cambia su archivo de manifiesto. Por ejemplo, si editas un recurso de imagen o cambias una función de JavaScript, esos cambios no se volverán a almacenar en caché. Debes modificar el archivo de manifiesto para informarle al navegador que actualice los archivos almacenados en caché.

Evita usar una marca de tiempo que se actualice continuamente o una cadena aleatoria para forzar las actualizaciones cada vez. El manifiesto se verifica dos veces durante una actualización, una vez al principio y otra después de que se hayan actualizado todos los archivos almacenados en caché. Si el manifiesto cambió durante la actualización, es posible que el navegador haya recuperado algunos archivos de una versión y otros de otra, por lo que no aplica la caché y vuelve a intentarlo más tarde.

Aunque se actualice la caché, el navegador no usará esos archivos hasta que se actualice la página, ya que las actualizaciones se producen después de que se carga la página desde la versión actual de la caché.

Un manifiesto puede tener tres secciones distintas: CACHE, NETWORK y FALLBACK.

CACHE:
Esta es la sección predeterminada para las entradas. Los archivos que se enumeran en este encabezado (o inmediatamente después de CACHE MANIFEST) se almacenarán en caché de forma explícita después de que se descarguen por primera vez. NETWORK:
Los archivos que se enumeran en esta sección pueden provenir de la red si no están en la caché. De lo contrario, no se usa la red, incluso si el usuario está en línea. Aquí puedes incluir URLs específicas en la lista de entidades permitidas o simplemente usar "", que permite todas las URLs. La mayoría de los sitios necesitan "". FALLBACK:
Una sección opcional que especifica las páginas de resguardo si no se puede acceder a un recurso. El primer URI es el recurso y el segundo es el resguardo que se usa si la solicitud de red falla o genera errores. Ambos URIs deben tener el mismo origen que el archivo de manifiesto. Puedes capturar URLs específicas, pero también prefijos de URL. "images/large/" capturará fallas de URLs como "images/large/whatever/img.jpg".

En el siguiente manifiesto, se define una página "catch-all" (offline.html) que se mostrará cuando el usuario intente acceder a la raíz del sitio sin conexión. También declara que todos los demás recursos (p. ej., los que se encuentran en un sitio remoto) requieren una conexión a Internet.

CACHE MANIFEST
# 2010-06-18:v3

# Explicitly cached entries
index.html
css/style.css

# offline.html will be displayed if the user is offline
FALLBACK:
/ /offline.html

# All other resources (e.g. sites) require the user to be online.
NETWORK:
*

# Additional resources to cache
CACHE:
images/logo1.png
images/logo2.png
images/logo3.png

Actualiza la caché

Una vez que una aplicación no tiene conexión, permanece almacenada en caché hasta que ocurre una de las siguientes situaciones:

  1. El usuario borra el almacenamiento de datos de su navegador para tu sitio.
  2. Se modifica el archivo de manifiesto. Nota: La actualización de un archivo incluido en el manifiesto no significa que el navegador volverá a almacenar en caché ese recurso. Se debe alterar el archivo de manifiesto.

Estado de la caché

El objeto window.applicationCache es tu acceso programático a la caché de la app del navegador. Su propiedad status es útil para verificar el estado actual de la caché:

var appCache = window.applicationCache;

switch (appCache.status) {
case appCache.UNCACHED: // UNCACHED == 0
return 'UNCACHED';
break;
case appCache.IDLE: // IDLE == 1
return 'IDLE';
break;
case appCache.CHECKING: // CHECKING == 2
return 'CHECKING';
break;
case appCache.DOWNLOADING: // DOWNLOADING == 3
return 'DOWNLOADING';
break;
case appCache.UPDATEREADY:  // UPDATEREADY == 4
return 'UPDATEREADY';
break;
case appCache.OBSOLETE: // OBSOLETE == 5
return 'OBSOLETE';
break;
default:
return 'UKNOWN CACHE STATUS';
break;
};

Para verificar de forma programática si hay actualizaciones en el manifiesto, primero llama a applicationCache.update(). Esta acción intentará actualizar la caché del usuario (lo que requiere que el archivo de manifiesto haya cambiado). Por último, cuando applicationCache.status esté en su estado UPDATEREADY, llamar a applicationCache.swapCache() intercambiará la caché anterior por la nueva.

var appCache = window.applicationCache;

appCache.update(); // Attempt to update the user's cache.

...

if (appCache.status == window.applicationCache.UPDATEREADY) {
appCache.swapCache();  // The fetch was successful, swap in the new cache.
}

La buena noticia es que puedes automatizar este proceso. Para actualizar a los usuarios a la versión más reciente de tu sitio, configura un objeto de escucha para supervisar el evento updateready en la carga de la página:

// Check if a new cache is available on page load.
window.addEventListener('load', function(e) {

window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
    // Browser downloaded a new app cache.
    if (confirm('A new version of this site is available. Load it?')) {
    window.location.reload();
    }
} else {
    // Manifest didn't changed. Nothing new to server.
}
}, false);

}, false);

Eventos de AppCache

Como es de esperar, se exponen eventos adicionales para supervisar el estado de la caché. El navegador activa eventos para acciones como el progreso de descarga, la actualización de la caché de la app y las condiciones de error. El siguiente fragmento configura objetos de escucha de eventos para cada tipo de evento de caché:

function handleCacheEvent(e) {
//...
}

function handleCacheError(e) {
alert('Error: Cache failed to update!');
};

// Fired after the first cache of the manifest.
appCache.addEventListener('cached', handleCacheEvent, false);

// Checking for an update. Always the first event fired in the sequence.
appCache.addEventListener('checking', handleCacheEvent, false);

// An update was found. The browser is fetching resources.
appCache.addEventListener('downloading', handleCacheEvent, false);

// The manifest returns 404 or 410, the download failed,
// or the manifest changed while the download was in progress.
appCache.addEventListener('error', handleCacheError, false);

// Fired after the first download of the manifest.
appCache.addEventListener('noupdate', handleCacheEvent, false);

// Fired if the manifest file returns a 404 or 410.
// This results in the application cache being deleted.
appCache.addEventListener('obsolete', handleCacheEvent, false);

// Fired for each resource listed in the manifest as it is being fetched.
appCache.addEventListener('progress', handleCacheEvent, false);

// Fired when the manifest resources have been newly redownloaded.
appCache.addEventListener('updateready', handleCacheEvent, false);

Si el archivo de manifiesto o un recurso especificado en él no se descargan, fallará toda la actualización. En caso de que se produzca una falla de este tipo, el navegador seguirá usando la caché de la aplicación anterior.

Referencias