AppCache, manifest, mundo offline e ServiceWorker

Na última BrazilJS, agosto de 2014, tivemos uma palestra do Renato Mangini sobre ServiceWorker, uma API recente que está movimentando bastante os core teams dos browsers modernos.

O Renato nos mostrou algumas peculiaridades da API e demonstrou alguns exemplos de possibilidades que podemos ter com ela. Além de resolver um problema crônico e bizarro de algumas API's, como a Appcache, ele vêm com novas propostas como o Response que modela HTTP responses (o que significa emular requests!) entre outras disponíveis no draft da W3C http://www.w3.org/TR/service-workers/.

Antes de falar mais sobre a API e especificamente do Appcache, precisamos saber como trabalhamos com o controle do cache atualmente.

Appcache

Talvez você já tenha lido algo sobre, mas vou pincelar aqui o que o ApplicationCache faz.

Manifest

Por mais que alguns considerem isso gambiarra, acredito que esse é o ponto forte do ApplicationCache. O Manifest é um arquivo de texto com a extensão .appcache que contém uma lista de assets/arquivos que serão ou não cacheados.

Como funciona?

Você define esse arquivo com a seguinte estrutura:

CACHE MANIFEST

# Versão 1.0
# Sempre definimos versão ou uma - logo abaixo vou explicar o porquê

# aqui vão arquivos/caminhos que sempre "buscarão" a rede

NETWORK
/files/download/1
/files/download/2


# aqui vão arquivos/caminhos a serem cacheados

CACHE
/main/home
/main/app.js

Basicamente temos isso e o FALLBACK, mas se você quer mais detalhes, sugiro que leia o artigo e os Resources citados no http://appcachefacts.info/ ou vá direto ao site da W3C que explica como o manifesto deve funcionar.

O controle de cache feito pelo manifesto funciona bem se você usa-lo corretamente. Você ainda tem o suporte dos métodos da API no browser como, o update e o swapCache.

The bad parts :(

O ponto é que esses métodos são insuficientes e a forma que o gerenciamento dos arquivos cacheados é feita impede o desenvolvedor de aplicar algumas features extremamente necessárias, como a limpeza do cache, por exemplo.

O Sérgio Lopes tem uma apresentação extremamente interessante falando sobre o funcionamento da API e os problemas e gambiarras que temos que fazer pra lidar com cache. Vários pontos ruins ali são levantados e como podemos contorná-los.

Só pra você ter uma ideia, segue uma lista com alguns probleminhas:

  • arquivos com parâmetros (querystrings) são considerados recursos diferentes;
  • o html com o atributo manifesto sempre será cacheado;
  • você precisa declarar TODOS os recursos do site pra que o offline funcione - uma alternativa é usar no NETWORK o * pra que ele considere tudo o que não foi listado dentro de CACHE ou FALLBACK como recurso disponível online;
  • pra mudar algum asset que foi atualizado no servidor, você precisa gerar um novo manifesto com algum item de controle no topo do arquivo (como por exemplo uma data ou uma version - como eu fiz no exemplo logo acima);
  • se há alterações, o usuário precisa atualizar a página pra recebê-las;
  • nada é controlado por API, tudo fica no arquivo;
  • não dá pra atualizar UM arquivo. Se mudar o manifesto TUDO vai ser baixado de novo;
  • se algo do FALLBACK fôr carregado online, sempre esse recurso será carregado online.

Viu quantos problemas aí? Pois é...

A grande falha, na minha opinião, é que praticamente tudo fica na mão do browser e isso deixa o desenvolvedor de mãos atadas.

Vamos ver então o papel da futura nova API ServiceWorker nessa conversa toda.

Como o ServiceWorker resolve isso tudo

Primeiramente, a solução proposta por Google, Mozilla, Samsung e outras gigantes, abrange várias outras "sub-API's" (não sei se é o correto chamar assim) dentro do ServiceWorker, como Request, Response dentre outras. Você pode conferir o andamento da documentação através desse link: https://slightlyoff.github.io/ServiceWorker/spec/service_worker/. Ainda há vários outros planos pra ela, mas a princípio isso é o que temos atualmente.

No caso de Cache a solução proposta é bem mais robusta, menos penosa e ao mesmo tempo mais simples que o ApplicationCache propõe hoje.

O que é esse ServiceWorker?

O ServiceWorker lembra muito o WebWorker e sua proposta de distribuir responsabilidades fora do escopo de uma página. Enquanto o WebWorker lida com a concorrência em JavaScript o ServiceWorker também tem a mesma abordagem: delegar tarefas para outra camada, fora da aplicação principal, controlando a chegada e saída de informações solicitadas por requests. É quase um proxy dentro da sua aplicação.

Isso vem à tona com a força que aplicações mobile tem tido e das inúmeras discussões sobre um App nativo versus um web browser.

O papel do ServiceWorker é assumir parte do controle da camada de request do browser, possibilitando emular e debuggar requisições. Você pode ver alguns exemplos e um vídeo bem bacana do Jake Archibald falando sobre isso no Google Summit 2014 aqui.

Como ele resolve o problema appCache

Vejamos um exemplo de como registraríamos um ServiceWorker que controlaria o cache da nossa aplicação.

Instanciando um ServiceWorker

navigator.serviceWorker.register("/assets/worker_app.js").then(
    function ( serviceWorker ) {
        serviceWorker.postMessage("ServiceWorker instalado com sucesso.");
    },
    function ( error ) {
        console.error("Ops.. não rolou a instalação do ServiceWorker", error);
    });

Percebeu um Promise ali né? Pois é. Estamos falando de EcmaScript 6, por isso vá com calma e nem invente de subir isso em produção. Nos sites mais modernos já temos suporte e até existe um polyfill, mas... na minha opinião você deve evitar coisas muito experimentais em produtos ou sites mais maduros.

Continuando, depois de termos nosso ServiceWorker registrado, podemos adicionar listeners pra executar determinadas ações. Veja como ficaria nosso ServiceWorker:

# /assets/worker_app.js

// SEMPRE defina uma version!
this.version = 1;

// Listener para a instalação do ServiceWorker
this.addEventListener("install",
    function ( e ) {
        var myResources = new Cache (
            "/index.html",
            "/assets/app.js",
            "/assets/logo.png",
            "/data/posts.json"
        );

        e.waitUntil(myResources.ready());
        caches.set("caches-" + this.version, myResources);
    }
);

// Listener de um request
this.addEventListener("fetch",
    function ( e ) {

        console.log("URL requisitada -> ", e.requrest.url);

        e.respondWith(
            caches.match( e.request )
                .catch( function () {
                    return e.default;
                })
                .catch( function () {
                    return caches.match("/data/posts_fallback.json");
                })
        );
    }
);

Viu que agora o cache está em nossas mãos? Agora estamos brincando com controle disso definitivamente! Resumindo: o poder é de vocês!

E no caso de atualizações ou de novas versões de arquivos, segue um exemplo abaixo:

# /assets/worker_app.js

this.version = 2;

this.addEventListener("install",
    function ( e ) {
        var myResources = new Cache (
            "/index.html",
            "/assets/app.js",
            "/assets/logo.png",
            "/data/posts.json"
        );

        e.waitUntil(myResources.ready());

        caches.set("caches-" + this.version, myResources);

        if (parseInt(e.previousVersion) == parseInt(this.version)) {
            e.replace();
        }
    }
);

Você deve estar pensando que isso dá muito mais trabalho que o manifesto. Tenho que concordar com esse pensamento, mas os benefícios de controle que isso traz são muito grandes se comparados ao tempo que você vai gastar em codificar isso. Nesse caso, o complicado é lidar com as falhas do ApplicationCache em browsers dos seus clientes.

O mundo real

Tivemos vários problemas pra lidar com offline num aplicativo que fizemos para a Philips. O app era pra registrar interesses nos produtos deles em feiras e exposições que a Philips participa.

O grande problema dessas feiras é a disponibilidade de internet seja por Wi-fi ou 3G (nem falo de 4G porque né...). Eles procuraram a gente (Mustache Labs) em busca de uma solução pra isso, seja usando um app nativo ou web. É claro que sugerimos web, pois é o nosso core atualmente e não estamos tão acostumados assim com desenvolvimento para Android, iOS ou até o PhoneGap.

Nós usamos Rails pra persistir os dados e toda a parte de sincronia foi feita usando Backbone.js mais o Backbone DualStorage.

Os problemas que tivemos foram relacionados a atualização das versões de arquivos JavaScript e até mesmo de CSS e imagens. Era um parto os usuários conseguirem atualizar a versão do aplicativo nos browsers que utilizavam... Tivemos muita dor de cabeça e até hoje ainda temos alguns problemas relativos às primeiras versões que lançamos.

Com o ServiceWorker vindo aí, não há dúvidas que esse cenário mudará. Até mesmo pra essa aplicação faremos um teste com ele e, com certeza postarei algo sobre essa experiência aqui!

Observações sobre o ServiceWorker

Um dos pontos a considerar em usar o ServiceWorker é que ele só funcionará com SSL. Isso evita brechas de segurança e cria um ambiente mais seguro pra emular os requests, mas dificulta um pouco a aplicação dele em sites menores - já que a maioria nem pensa em usar um certificado nos sites.

Outro fator importantíssimo é que tudo isso ainda está em Draft na W3C e só alguns browsers estão implementando. Você pode conferir quem já implementa o quê de ServiceWorker aqui https://jakearchibald.github.io/isserviceworkerready/.

Em breve teremos novidades incríveis como Background sync (!), Geofencing (!!) e Bluetooth (!!!).

Awesome!

Resumindo

ServiceWorker está chegando e vai resolver várias pendências deixadas pelo ApplicationCache. Além disso já traz novas funcionalidades que vão deixar aplicações web empatadas com as nativas - é claro que me refiro a coisas mais intrínsecas dos devices como Bluetooth, Push Notifications entre outras coisas.

Ainda é draft, mas você já deve começar a estudar isso logo, porque os browsers estão investindo bastante pra isso acontecer o mais rápido possível!

Ah! Comenta aí embaixo se tiver alguma dúvida ou se escrevi alguma burrada :)

Recursos pra estudar

comments powered by Disqus