Guia para iniciantes sobre como usar o cache do aplicativo

Introdução

É cada vez mais importante que os aplicativos da Web sejam acessíveis off-line. Sim, todos os navegadores podem armazenar páginas e recursos em cache por longos períodos, mas podem remover itens individuais do cache a qualquer momento para abrir espaço para outras coisas. O HTML5 resolve alguns dos problemas de estar off-line com a interface ApplicationCache. Usar a interface de cache oferece três vantagens ao aplicativo:

  1. Navegação off-line: os usuários podem navegar pelo site completo quando estão off-line.
  2. Velocidade: os recursos vêm diretamente do disco, sem ir para a rede.
  3. Resiliência: se o site ficar indisponível para "manutenção" (por exemplo, alguém quebra tudo acidentalmente), os usuários vão ter a experiência off-line.

O cache do aplicativo (ou AppCache) permite que um desenvolvedor especifique quais arquivos o navegador precisa armazenar em cache e disponibilizar para usuários off-line. O app vai carregar e funcionar corretamente, mesmo que o usuário pressione o botão de atualização enquanto estiver off-line.

O arquivo de manifesto do cache

O arquivo de manifesto de cache é um arquivo de texto simples que lista os recursos que o navegador precisa armazenar em cache para acesso off-line.

Como fazer referência a um arquivo de manifesto

Para ativar o cache de um app, inclua o atributo de manifesto na tag html do documento:

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

O atributo manifest precisa ser incluído em todas as páginas do seu aplicativo da Web que você quer armazenar em cache. O navegador não armazena em cache uma página se ela não contiver o atributo manifest, a menos que ele esteja listado explicitamente no arquivo de manifesto. Isso significa que qualquer página para a qual o usuário navegue e que inclua um manifest será adicionada implicitamente ao cache do aplicativo. Portanto, não é necessário listar todas as páginas no manifesto. Se uma página apontar para um manifesto, não será possível evitar que ela seja armazenada em cache.

Para conferir os URLs controlados pelo cache do aplicativo, acesse about://://appcache-internals/ no Chrome. Aqui, você pode limpar os caches e conferir as entradas. Há ferramentas para desenvolvedores semelhantes no Firefox.

O atributo manifest pode apontar para um URL absoluto ou um caminho relativo, mas um URL absoluto precisa estar na mesma origem que o aplicativo da Web. Um arquivo de manifesto pode ter qualquer extensão, mas precisa ser enviado com o tipo MIME correto (consulte abaixo).

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

Um arquivo de manifesto precisa ser veiculado com o tipo mime text/cache-manifest. Talvez seja necessário adicionar um tipo de arquivo personalizado ao servidor da Web ou à configuração .htaccess.

Por exemplo, para veicular esse tipo MIME no Apache, adicione esta linha ao arquivo de configuração:

AddType text/cache-manifest .appcache

Ou no arquivo app.yaml no Google App Engine:

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

Esse requisito foi removido da especificação há algum tempo e não é mais necessário nas versões mais recentes do Chrome, Safari e Firefox. No entanto, você vai precisar do mime-type para que ele funcione em navegadores mais antigos e no IE11.

Estrutura de um arquivo de manifesto

O manifesto é um arquivo separado que você vincula pelo atributo "manifest" no elemento HTML. Um manifesto simples tem esta aparência:

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

Este exemplo vai armazenar em cache quatro arquivos na página que especifica esse arquivo de manifesto.

Há algumas coisas a serem observadas:

  • A string CACHE MANIFEST é a primeira linha e é obrigatória.
  • Os arquivos podem ser de outro domínio
  • Alguns navegadores impõem restrições à quantidade de cota de armazenamento disponível para seu app. No Chrome, por exemplo, o AppCache usa um pool compartilhado de armazenamento TEMPORARÁRIO que outras APIs off-line podem compartilhar. Se você estiver criando um app para a Chrome Web Store, o uso do unlimitedStorage remove essa restrição.
  • Se o manifesto retornar um 404 ou 410, o cache será excluído.
  • Se o download do manifesto ou de um recurso especificado nele falhar, todo o processo de atualização do cache vai falhar. O navegador continuará usando o cache do aplicativo antigo em caso de falha.

Vamos analisar um exemplo mais complexo:

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

As linhas que começam com '#' são linhas de comentário, mas também podem servir para outra finalidade. O cache de um aplicativo só é atualizado quando o arquivo de manifesto muda. Por exemplo, se você editar um recurso de imagem ou mudar uma função JavaScript, essas mudanças não serão armazenadas em cache novamente. É necessário modificar o arquivo de manifesto para informar ao navegador que os arquivos armazenados em cache precisam ser atualizados.

Evite usar um carimbo de data/hora que é atualizado continuamente ou uma string aleatória para forçar as atualizações sempre. O manifesto é verificado duas vezes durante uma atualização, uma no início e outra depois que todos os arquivos em cache são atualizados. Se o manifesto foi alterado durante a atualização, é possível que o navegador tenha buscado alguns arquivos de uma versão e outros arquivos de outra. Portanto, ele não aplica o cache e tenta novamente mais tarde.

Embora o cache seja atualizado, o navegador não vai usar esses arquivos até que a página seja atualizada, porque as atualizações acontecem depois que a página é carregada da versão atual do cache.

Um manifesto pode ter três seções distintas: CACHE, NETWORK e FALLBACK.

CACHE:
Esta é a seção padrão para entradas. Os arquivos listados neste cabeçalho (ou imediatamente após CACHE MANIFEST) serão armazenados em cache de forma explícita após o download pela primeira vez. NETWORK:
Os arquivos listados nesta seção podem vir da rede se não estiverem no cache. Caso contrário, a rede não será usada, mesmo que o usuário esteja on-line. É possível adicionar URLs específicos à lista de permissões aqui ou simplesmente "", que permite todos os URLs. A maioria dos sites precisa de "". FALLBACK:
Uma seção opcional que especifica páginas de substituição caso um recurso esteja inacessível. O primeiro URI é o recurso, e o segundo é o substituto usado se a solicitação de rede falhar ou apresentar erros. Os dois URIs precisam ter a mesma origem do arquivo de manifesto. Você pode capturar URLs específicos, mas também prefixos de URL. "images/large/" captura falhas de URLs como "images/large/whatever/img.jpg".

O manifesto a seguir define uma página "catch-all" (offline.html) que será exibida quando o usuário tentar acessar a raiz do site off-line. Ele também declara que todos os outros recursos (por exemplo, os que estão em um site remoto) precisam de uma conexão de 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

Como atualizar o cache

Quando um aplicativo fica off-line, ele permanece em cache até que uma das seguintes situações aconteça:

  1. O usuário limpa o armazenamento de dados do navegador para seu site.
  2. O arquivo de manifesto é modificado. Observação: atualizar um arquivo listado no manifesto não significa que o recurso será armazenado em cache novamente pelo navegador. O arquivo de manifesto precisa ser alterado.

Status do cache

O objeto window.applicationCache é o acesso programático ao cache do app do navegador. A propriedade status é útil para verificar o estado atual do cache:

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 programaticamente se há atualizações para o manifesto, primeiro chame applicationCache.update(). Essa ação tentará atualizar o cache do usuário, o que exige que o arquivo de manifesto seja alterado. Por fim, quando o applicationCache.status estiver no estado UPDATEREADY, chamar applicationCache.swapCache() vai trocar o cache antigo pelo novo.

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.
}

A boa notícia é que você pode automatizar isso. Para atualizar os usuários para a versão mais recente do seu site, defina um listener para monitorar o evento updateready no carregamento da 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 do AppCache

Como esperado, eventos adicionais são expostos para monitorar o estado do cache. O navegador dispara eventos para itens como andamento do download, atualização do cache do app e condições de erro. O snippet a seguir configura listeners de eventos para cada tipo de evento de cache:

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);

Se o arquivo de manifesto ou um recurso especificado nele não for transferido por download, toda a atualização vai falhar. O navegador vai continuar usando o cache antigo do aplicativo em caso de falha.

Referências