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

HTML5 Rocks

Introducción

Cada vez es más importante poder acceder a las aplicaciones web sin conexión. Es cierto que todos los navegadores tienen mecanismos de almacenamiento en caché, pero esos sistemas no son fiables y no siempre funcionan como debieran. HTML5 permite resolver algunas de las molestias asociadas al trabajo sin conexión mediante la interfaz ApplicationCache.

A continuación se indican las tres ventajas que conlleva el uso de la interfaz de caché para una aplicación.

  1. Navegación sin conexión: los usuarios pueden explorar todo el sitio web sin conexión.
  2. Velocidad: los recursos almacenados en caché son locales y, por tanto, se cargan más rápido.
  3. Reducción de carga del servidor: el navegador solo descarga recursos del servidor que han cambiado.

La caché de aplicación (o AppCache) permite que el desarrollador especifique los archivos que el navegador debe almacenar en caché y poner a disposición de los usuarios que trabajen sin conexión. La aplicación se cargará y funcionará correctamente, incluso si el usuario pulsa el botón de actualización mientras trabaja sin conexión.

El archivo de manifiesto de caché

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

Referencia a un archivo de manifiesto

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

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

El atributo manifest debe estar incluido en todas las páginas de tu aplicación web que quieras que se almacenen en caché. El navegador no almacenará en caché ninguna página que no contenga el atributo manifest (a menos que esa página aparezca explícitamente en el propio archivo de manifiesto). Así pues, cualquier página a la que acceda el usuario que incluya un atributo manifest se añadirá implícitamente a la caché de la aplicación. Por tanto, no es necesario incluir cada una de las páginas en el archivo de manifiesto.

El atributo manifest puede señalar a una URL absoluta o a una ruta relativa, pero las URL absolutas deben tener el mismo origen que la aplicación web. Un archivo de manifiesto puede tener cualquier extensión, pero se debe mostrar con el tipo MIME correcto (indicado a continuación).

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

El tipo MIME con el que se deben mostrar los archivos de manifiesto es text/cache-manifest. Es posible que tengas que añadir un tipo de archivo personalizado a la configuración de .htaccess o de tu servidor web.

Por ejemplo, para mostrar este tipo MIME en Apache, añade la siguiente línea a tu archivo de configuración:

AddType text/cache-manifest .appcache

También puedes añadir las siguientes líneas a tu archivo "app.yaml" en Google App Engine:

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

Estructura de un archivo de manifiesto

A continuación se muestra un ejemplo de un archivo de manifiesto sencillo:

CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js

El archivo de manifiesto del ejemplo permite almacenar en caché los cuatro archivos de la página especificados.

Se deben tener en cuenta un par de cosas:

  • La cadena CACHE MANIFEST debe aparecer en la primera línea y es obligatoria.
  • Los sitios no pueden tener más de 5 MB de datos almacenados en caché. Sin embargo, si creas una aplicación para Chrome Web Store, puedes utilizar unlimitedStorage para eliminar esta restricción.
  • Si no se puede descargar el archivo de manifiesto o algún recurso especificado en él, fallará todo el proceso de actualización de la caché. En caso de fallo, el navegador seguirá utilizando la antigua caché de la aplicación.

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:
login.php
/myapi
http://api.twitter.com

# 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
*.html /offline.html

Las líneas que comienzan con el símbolo "#" son líneas de comentarios, pero también pueden tener otra finalidad. La caché de una aplicación solo se actualiza cuando se produce algún cambio en su archivo de manifiesto. Por ejemplo, si se edita un recurso de imagen o se modifica una función JavaScript, esos cambios no se reflejarán en el contenido almacenado en caché. Para que el navegador actualice los archivos almacenados en caché, se debe modificar el propio archivo de manifiesto. Una forma de asegurarte de que los usuarios tengan la última versión de tu software es crear una línea de comentario con un valor hash generado correspondiente al número de versión de los archivos o una marca de tiempo. También puedes actualizar la caché mediante programación una vez que esté lista una nueva versión, tal como se indica en la sección sobre actualización de la caché.

Un archivo de manifiesto puede incluir tres secciones: CACHE, NETWORK y FALLBACK.

CACHE:
Esta es la sección predeterminada para las entradas. Los archivos incluidos en esta sección (o inmediatamente después de CACHE MANIFEST) se almacenarán en caché explícitamente después de descargarse por primera vez.
NETWORK:
Los archivos incluidos en esta sección son recursos permitidos que requieren conexión al servidor. En todas las solicitudes enviadas a estos recursos se omite la caché, incluso si el usuario está trabajando sin conexión. Se pueden utilizar caracteres comodín.
FALLBACK:
Se trata de una sección opcional en la que se especifican páginas alternativas en caso de no poder acceder a un recurso. La primera URI corresponde al recurso y la segunda, a la página alternativa. Ambas URI deben estar relacionadas y tener el mismo origen que el archivo de manifiesto. Se pueden utilizar caracteres comodín.

Nota: estas secciones se pueden incluir en cualquier orden y pueden aparecer varias veces en cada archivo de manifiesto.

En el archivo de manifiesto que se muestra a continuación se define una página "general" (offline.html) que aparecerá cuando el usuario intente acceder al directorio raíz del sitio sin conexión. También se indica que todos los demás recursos (por ejemplo, los que se encuentran en sitios remotos) requieren 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

Nota: el archivo HTML al que hace referencia el archivo de manifiesto se almacena en caché automáticamente. No es necesario incluirlo en el archivo de manifiesto, pero se recomienda hacerlo.

Nota: los archivos de manifiesto de caché anulan los encabezados de caché de HTTP y las restricciones de almacenamiento en caché aplicables a las páginas mostradas a través de SSL. Por tanto, se puede hacer que las páginas mostradas a través de https funcionen sin conexión.

Actualización de la memoria caché

Una vez que una aplicación pasa a funcionar sin conexión, se queda almacenada en caché hasta que se da alguna de las siguientes circunstancias:

  1. El usuario borra el almacenamiento de datos del sitio en el navegador.
  2. Se modifica el archivo de manifiesto. Nota: cuando se actualiza un archivo incluido en el archivo de manifiesto, el navegador no tiene por qué volver a almacenar necesariamente ese recurso en caché. Se debe sustituir el propio archivo de manifiesto.
  3. La caché de la aplicación se actualiza mediante programación.

Estado de la caché

El objeto window.applicationCache permite acceder (mediante programación) a la caché de aplicación del navegador. Su propiedad status permite comprobar el estado de la memoria 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 actualizar la caché mediante programación, primero se debe hacer una llamada a applicationCache.update(). Al hacer esa llamada, se intentará actualizar la caché del usuario (para lo cual será necesario que haya cambiado el archivo de manifiesto). Finalmente, cuando el estado de applicationCache.status sea UPDATEREADY, al llamar a applicationCache.swapCache(), se sustituirá la antigua caché 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.
}

Nota: al utilizar update() y swapCache() de este modo, no se muestran los recursos actualizados a los usuarios. El flujo indicado solo sirve para pedirle al navegador que busque un nuevo archivo de manifiesto, que descargue el contenido actualizado que se especifica y que actualice la caché de la aplicación. Por tanto, la página se tiene que volver a cargar dos veces para que se muestre el nuevo contenido a los usuarios: una vez para extraer una nueva caché de aplicación y otra para actualizar el contenido de la página.

Afortunadamente, se puede evitar este doble trabajo de recarga. Para que los usuarios puedan acceder a la versión más reciente del contenido de tu sitio, puedes establecer un detector que controle el evento updateready cuando se cargue 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.
      // Swap it in and reload the page to get the new hotness.
      window.applicationCache.swapCache();
      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 caché de aplicación

Como cabría esperar, hay algunos eventos adicionales que permiten controlar el estado de la caché. El navegador activa eventos para una serie de acciones (como el progreso de las descargas, la actualización de la caché de las aplicaciones y los estados de error). El siguiente fragmento permite establecer detectores 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 no se puede descargar el archivo de manifiesto o algún recurso especificado en él, fallará todo el proceso de actualización. Si se produce ese fallo, el navegador seguirá utilizando la antigua caché de la aplicación.

Referencias

Comments

0