Acelere o carregamento de suas páginas

Também conhecido como o Santo Graal das páginas de internet, encontrar o carregamento perfeito não é tarefa fácil.

por Jean Carlo Emer 25/03/2014 ~ 9 min. / 1790 palavras

Muita coisa já foi escrita sobre este assunto, originalmente em português temos o renomado guia Como perder peso no browser cujos autores são feras e a série intitulada Performance front-end aqui mesmo no Tableless. As iniciativas gringas são muitas com destaque ao YSlow e às práticas do Yahoo! para melhorar performance.

Neste ponto, se ainda continua nesta leitura, você deve estar se perguntando se existe alguma técnica que não é coberta por alguma destas referências. Há sim. Porém já deixo o aviso, o que veremos a seguir não substitui outras técnicas voltadas a ganho de performance, assim como muitas outras, é apenas uma técnica complementar.

Carregamento especulativo

Uma tentativa de acelerar o carregamento já foi vendida no Brasil como uma funcionalidade incrível creditada a discadores de internet. Sim, discadores. Os pacotes de software incluíam um navegador especial. A função deste era identificar os hiperlinks já no carregamento da página e requisitar por eles sem que o usuário tomasse conhecimento. Assim, quando o usuário seguisse algum hiperlink, o seu conteúdo já estava disponível.

O resultado desta técnica é um tanto desastroso por duas perspectivas. Muito do conteúdo requisitado nunca era utilizado desperdiçando banda de internet e processamento do cliente e servidor. E em segundo, porque já naquele tempo as páginas faziam mal uso de hiperlinks para operar manipulação e exclusão de recursos.

Esta técnica já não é mais utilizada, provando que requisitar mais do que se precisa não é uma solução inteligente.

Carregando apenas conteúdo

Nas aplicações tradicionais, o carregamento de JavaScript e CSS, nossos assets, despendem até metade do tempo total de carregamento da página. Se considerarmos que, para tirar proveito do cache, estes assets serão os mesmos em diferentes páginas. O próximo passo é reaproveitar uma única página durante a navegação.

A técnica consiste em alterar o comportamento padrão dos hyperlinks fazendo com que o endereço indicado no atributo href seja requisitado assincronamente. O resultado da requisição é analisado e apenas o conteúdo de interesse é substituído. O principal ganho de performance se deve ao fato das folhas de estilo e scripts não serem requisitados durante a navegação.

Bibliotecas

jQuery PJAX

Criada por um dos fundadores do GitHub, a biblioteca PJAX implementa a técnica utilizando jQuery. Para testar seu funcionamento, basta navegar por um repositório no próprio GitHub.

A biblioteca permite indicar quais hyperlinks terão seu comportamento modificado e qual o container que deve ser utilizado para depositar o conteúdo retornado pela requisição. O conteúdo retornado pela requisição deve ser tratado no back-end para retornar estritamente o que precisa ser depositado no container. Isto é possível graças a um cabeçalho adicionado a requisição que garante sua identificação no back-end. Mesmo que a intenção seja substituir o <body>, é aconselhado remover o <head> mantendo apenas a tag <title>. Isto garante um ganho de performance ainda mais significativo.

O Turbolinks é um misto de biblioteca JavaScript e código back-end que implementa a  técnica de carregamento de conteúdo no Ruby on Rails sem depender de jQuery. A gem, como são chamados os pacotes de Ruby, é padrão a partir da versão 4.0 do framework.

A biblioteca foi desenvolvida pela 37Signals para ser utilizada na versão mobile do seu principal produto, o Campfire. O que atesta que a técnica é praticável em dispositivos móveis modernos.

Diferente da biblioteca PJAX, o Turbolinks não permite que seja configurado o container de destino do conteúdo, todo o conteúdo do <body> é substituído. Por causa disto, a aplicação não precisa sofrer nenhuma modificação no seu back-end para utilizar a gem: o conteúdo esperado é o mesmo de uma requisição tradicional de página. Como veremos a seguir, os desafios para se utilizar o Turbolinks e mesmo a PJAX, residem no front-end da aplicação

Como a técnica é possível

Requisições assíncronas já são usadas frequentemente e enfrentam praticamente nenhum problema de suporte. Nos primórdios, iframes e API de ActiveXObject eram usados para possibilitar este tipo de requisição. Atualmente, grande parte dos navegadores suportam a API de XMLHttpRequest apesar da especificação estar em rascunho desde 2006.

Note que a técnica é fundamentalmente calcada na mudança da barra de endereço sem que resulte no carregamento de uma nova página. A barra de endereço está intimamente ligada com a seção de histórico onde os navegadores armazenam as páginas acessadas. Antigamente, este histórico podia apenas ser retrocedido e avançado através da interface JavaScript window.history.

Uma nova especificação, associada com o HTML5, permite manipular o histórico e consequentemente a barra de endereço. Como se trata de uma funcionalidade nova, seu suporte é restrito a navegadores modernos. As bibliotecas PJAX e Turbolinks fazem uma detecção da funcionalidade e operam no modelo de navegação tradicional caso esta não esteja disponível.

A nova API de history permite adicionar novas entradas com a função window.history.pushState. A função recebe os parâmetros data e title, utilizados para referenciar esta entrada no histórico. O último parâmetro url se trata do endereço a ser mostrado na barra de endereço. A API também define um evento popstate que permite identificar quando o usuário navega por entradas adicionadas ao histórico. Vejamos o funcionamento com um exemplo:

window.addEventListener('popstate', function(event) {
  console.log(event.state);
}, false

window.history.pushState({ tableless: 'sample' }, 'Fake Post', 'https://tableless.com.br/fake-post');

Como já observamos, a execução da função pushState irá adicionar uma entrada no histórico de navegação e alterar a barra de endereço para https://tableless.com.br/fake-post. Na ocasião de o usuário retroceder o histórico de navegação, a barra de endereço será alterada para seu endereço inicial e o evento popstate será disparado. O valor da propriedade state do evento é aquele definido pelo parâmetro data na chamada de pushState. O console será preenchido com Object {tableless: "sample"}.

Dependendo da implementação da API no navegador, o evento popstate será disparado logo no carregamento da página. Neste caso, o console será preenchido com undefined.

Dicas importantes

Fazendo uso das bibliotecas, seu projeto ser tornará uma aplicação que carrega suas páginas assincronamente. Um pré requisito para o que discutiremos a seguir é compreender o comportamento dos scripts. Sempre que um script não é disponibilizado no documento, a única maneira de executá-lo é através da API do DOM. Nestes casos, o seu carregamento será assíncrono e sua execução é condicionada ao término do download. Note, não há garantia de ordem de execução quando temos mais de um script.

jQuery PJAX

A biblioteca analisa o conteúdo retornado pela requisição a procura de scripts. Caso o arquivo indicado pelo src ainda não faça parte da aplicação, o script é adicionado ao <head>. Apenas estes serão executados sem garantia de ordem.

O ideal é incluir os scripts no <head> e assistir aos eventos pjax:start e pjax:end, disparados imediatamente antes e depois de alterar o conteúdo,  para atribuir e remover comportamentos. Entenda que a biblioteca mantém em cache todos os conteúdos requisitados para agilizar a navegação pelo histórico. Isto matém ativos os comportamentos atribuídos. O impacto é ainda maior se o conteúdo assiste a eventos do <body> ou outros objetos globais. A remoção dos comportamentos passa a ser de extrema importância para evitar memory leaks.

A biblioteca executa todos os scripts contidos no <body> sem garantia de ordem. Um dos problemas mais comuns se dá quando a biblioteca é ativada em aplicações que seguem a boa prática de inserir os scripts antes do </body>. Os scripts que tenham alguma dependência, plugins jQuery, por exemplo, passam a depender da sorte para funcionar. A solução é mover os scripts para o <head> ou marcar aqueles que não devem ser executados com o atributo data-turbolinks-eval igual a false.

Uma prática comum quando posicionamos scripts no <head> é acessar o DOM utilizando funções como jQuery.ready para garantir que já esteja acessível. O maior impacto no uso do Turbolinks é que eventos de load não serão mais disparados durante a navegação, jQuery.ready não irá funcionar. A biblioteca dispara os eventos page:receive e page:load antes e depois de alterar o conteúdo. Os comportamentos precisam ser atribuídos com base nestes eventos.

A biblioteca ainda permite que os assets que contenham o atributo data-turbolinks-track sejam analisados a cada requisição. Caso uma mudanças seja identificada, a página é completamente recarregada.

Assim como a PJAX, o Turbolinks também implementa uma estratégia de cache. O conteúdo, quando trazido imediatamente do cache, dispara o evento page:restore. Em seguida, para atualizar o conteúdo, a requisição é refeita. Todas as mudanças de conteúdo, que sempre serão duas nestes casos, são seguidas dos eventos page:change e page:update.

Gerenciar todos estes eventos pode ser complicado, ainda mais se já estiver acostumado a utilizar jQuery.ready. A solução é adotar o plugin jQuery Turbolinks. Mas não se esqueça, a mesma dica de remoção dos comportamentos mencionada na seção a respeito da PJAX são válidas aqui para evitar memory leaks.

Um último desafio é fazer com que outras bibliotecas JavaScript sejam compatíveis com o Turbolinks. Serviços comuns como Google Analytics e widgets do Facebook e Twitter podem não funcionar adequadamente. Um projeto muito interessante chamado Turbolinks Compatibility apresenta estratégias para tornar compatível uma série de bibliotecas.

Futuro das bibliotecas

Formulários ainda são um problema, o Turbolinks não endereça este caso de uso e o PJAX apenas o faz de maneira experimental. O agravante é maior quando o formulário inclui envio de arquivos.

A especificação em rascunho do XMLHttpRequest Level 2 permite o envio de arquivos com informação de progresso através de requisições assíncronas. Esta é uma funcionalidade muito interessante para aplicações que fazem upload de muitos arquivos em série, por exemplo. O suporte da API já é muito bom em navegadores modernos.

Conclusão

A biblioteca PJAX é uma alternativa muito boa por permitir atualizar seções específicas da página. Ao mesmo tempo, para uma implementação inicial, a biblioteca permite também o carregamento total do conteúdo da página.

Turbolinks é uma ótima alternativa para implementar a técnica de carregamento de conteúdo em aplicações Ruby on Rails. Se você quiser ir um pouco além, podendo inclusive enviar formulários, a biblioteca Wiselinks pode ser uma ótima tentativa.

Evoluindo ainda mais a sua aplicação

Talvez não pareça tão natural, mas o próximo passo é utilizar uma biblioteca como o Backbone.js. Através dela é possível gerenciar cada um dos containers que podem sofrer atualização de conteúdo através do construtor Backbone.View.

Os endereços serão gerenciados pelo Backbone.Router que inclusive utiliza window.history.pushState. Note a semelhança, todas as páginas que o roteador conhece devem ser implementadas no back-end para quando o usuário acessar o endereço diretamente. O Backbone.js ainda disponibiliza os construtores de Model e Collection para gerenciar os dados da sua aplicação.

Por fim

Os ganhos em rapidez de carregamento podem ser bastante significativos, mas a aplicação precisa ser repensada e adequada. É preciso testar constantemente a aplicação em busca de memory leaks.  Apenas adicionar uma das bibliotecas de carregamento de conteúdo assíncrono e esperar que tudo funcione não é uma alternativa. Mas não deixe de tentar, o usuário agradece.