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

Introducción

Cada vez es 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 así se lo solicitan, pero el navegador puede expulsar elementos individuales de la caché en cualquier momento para hacer lugar a otras cosas. HTML5 soluciona algunas de las molestias de estar sin conexión con la interfaz ApplicationCache. Usar la interfaz de caché le brinda a tu aplicación tres ventajas:

  1. Navegación sin conexión: Los usuarios pueden navegar por todo el sitio cuando están sin conexión.
  2. Velocidad: los recursos vienen directamente del disco, sin viajes a la red.
  3. Resistencia: Si su sitio deja de funcionar por "mantenimiento" (por ejemplo, si alguien rompe todo por accidente), sus usuarios obtendrán la experiencia sin conexión.

La caché de aplicaciones (o AppCache) permite a un desarrollador especificar qué archivos debe almacenar el navegador en caché 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 la 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 acceder sin conexión.

Cómo hacer referencia a un archivo de manifiesto

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

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

Se debe incluir el atributo manifest en cada página de la aplicación web que quieres almacenar en caché. El navegador no almacena en caché una página si no contiene el atributo manifest (a menos que se indique explícitamente 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 incluir todas las páginas de tu manifiesto. Si una página apunta a un manifiesto, no hay forma de evitar que la página se almacene en caché.

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

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

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

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

Por ejemplo, para entregar este tipo de 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 excluyó de la especificación hace algún tiempo y ya no es necesario para las versiones más recientes de Chrome, Safari y Firefox. Sin embargo, el tipo MIME se necesita para funcionar en IE11 y navegadores más antiguos.

Estructura de un archivo de manifiesto

El manifiesto es un archivo separado que puedes vincular a través del atributo de manifiesto en el elemento html. Un manifiesto simple se ve más o menos así:

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 cuatro archivos en caché en la página que especifica este archivo de manifiesto.

Debes tener en cuenta lo siguiente:

  • La cadena CACHE MANIFEST es la primera línea y es obligatoria.
  • Los archivos pueden ser de otro dominio
  • Algunos navegadores imponen restricciones en 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 estás escribiendo una app para Chrome Web Store, usar unlimitedStorage elimina esa restricción.
  • Si el manifiesto muestra un error 404 o 410, se eliminará la caché.
  • Si el manifiesto o un recurso especificado en él no se descargan, fallará todo el proceso de actualización de la caché. El navegador seguirá usando la caché de la aplicación anterior en caso de que falle.

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 un símbolo "#" son líneas de comentarios, pero también pueden servir para 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 informar al navegador que actualice los archivos almacenados en caché.

Evita usar una marca de tiempo de actualización continua o una cadena aleatoria para forzar actualizaciones en cada ocasión. El manifiesto se comprueba dos veces durante una actualización, una al comienzo y otra después de que se actualizan 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 archivos de otra, por lo que no aplica la caché y vuelve a intentarlo más tarde.

Si bien la caché se actualiza, el navegador no usará esos archivos hasta que se actualice la página, ya que las actualizaciones ocurren después de que la página se carga 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 aparecen 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 indican en esta sección pueden provenir de la red si no están en la caché. De lo contrario, no se usará la red, incluso si el usuario está en línea. Puedes incluir URLs específicas en la lista blanca aquí o simplemente incluir "", que permite todas las URLs. La mayoría de los sitios necesita "". FALLBACK:
Una sección opcional que especifica las páginas de resguardo en caso de que no se pueda acceder a un recurso. El primer URI es el recurso, el segundo es el resguardo que se usa si la solicitud de red falla o presenta errores. Ambos URI 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 genérica (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 se encuentra sin conexión, permanece almacenada en caché hasta que ocurra una de las siguientes situaciones:

  1. Si el usuario borra el almacenamiento de datos del navegador para tu sitio.
  2. Se modificó el archivo de manifiesto. Nota: Actualizar un archivo que aparece en el manifiesto no significa que el navegador volverá a almacenar en caché ese recurso. Se debe modificar 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 comprobar de manera programática las actualizaciones del manifiesto, primero llama a applicationCache.update(). De esta forma, se intentará actualizar la caché del usuario (lo cual requiere que se modifique el archivo de manifiesto). Por último, cuando applicationCache.status esté en su estado UPDATEREADY, llamar a applicationCache.swapCache() cambiará 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 esto. Si quieres actualizar a los usuarios a la versión más reciente de tu sitio, configura un objeto de escucha para supervisar el evento updateready cuando se carga 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 como el progreso de la descarga, la actualización de la caché de la app y las condiciones de error. En el siguiente fragmento, se configuran 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 no se descargan, fallará toda la actualización. El navegador seguirá usando la caché de la aplicación anterior en caso de que ocurra una falla.

Referencias