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 , é aconselhado remover o
mantendo apenas a tag
. Isto garante um ganho de performance ainda mais significativo.
Turbolinks
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 é 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 . Apenas estes serão executados sem garantia de ordem.
O ideal é incluir os scripts no 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 ou outros objetos globais. A remoção dos comportamentos passa a ser de extrema importância para evitar memory leaks.
Turbolinks
A biblioteca executa todos os scripts contidos no 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
. 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
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 é 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.