Otimização da inicialização em JavaScript

À medida que criamos sites mais dependentes do JavaScript, às vezes pagamos por o que enviamos de maneiras que não são sempre fáceis de detectar. Neste artigo, vamos explicar por que um pouco de disciplina pode ajudar se você quiser que seu site carregue e seja interativo rapidamente em dispositivos móveis. Fornecer menos JavaScript pode significar menos tempo na transmissão de rede, menos tempo gasto na descompressão do código e menos tempo analisando e compilando esse JavaScript.

Rede

Quando a maioria dos desenvolvedores pensa no custo do JavaScript, eles pensam em termos de custo de download e execução. O envio de mais bytes de JavaScript pela rede leva mais tempo quanto mais lenta for a conexão do usuário.

Quando um navegador solicita um
recurso, ele precisa ser buscado e descompactado. No caso
de recursos como JavaScript, eles precisam ser analisados e compilados antes da
execução.

Isso pode ser um problema, já que o tipo de conexão de rede efetivo de um usuário pode não ser 3G, 4G ou Wi-Fi. Você pode estar no Wi-Fi de um café, mas conectado a um ponto de acesso de celular com velocidades 2G.

É possível reduzir o custo de transferência de rede do JavaScript com:

  • Enviar apenas o código necessário para o usuário.
  • Minificação
  • Compressão
    • No mínimo, use o gzip para compactar recursos baseados em texto.
    • Considere usar o Brotli ~q11. O Brotli é melhor que o gzip na proporção de compactação. Isso ajudou a CertSimple a economizar 17% no tamanho dos bytes JS compactados e a LinkedIn a economizar 4% nos tempos de carregamento.
  • Remover o código não utilizado.
  • Armazenamento em cache do código para minimizar as viagens de rede.
    • Use o armazenamento em cache HTTP para garantir que os navegadores armazenem as respostas em cache de forma eficaz. Determine a duração ideal para scripts (max-age) e forneça tokens de validação (ETag) para evitar a transferência de bytes inalterados.
    • O armazenamento em cache do service worker pode tornar a rede do app mais resilient e oferecer acesso rápido a recursos como o cache de código do V8.
    • Use o armazenamento em cache de longo prazo para evitar a necessidade de buscar novamente recursos que não mudaram. Se você estiver usando o Webpack, consulte hashing de nomes de arquivo.

Analisar/Compilar

Depois do download, um dos custos mais pesados do JavaScript é o tempo que um mecanismo JS leva para analisar/compilar esse código. No Chrome DevTools, a análise e a compilação fazem parte do tempo amarelo "Scripting" no painel "Performance".

ALT_TEXT_HERE

As guias "Bottom-Up" e "Call Tree" mostram os tempos de análise/compilação exatos:

ALT_TEXT_HERE
Painel "Performance" do Chrome DevTools > "Bottom-Up". Com as estatísticas de chamada de ambiente de execução do V8 ativadas, é possível conferir o tempo gasto em fases como Análise e compilação

Mas por que isso é importante?

ALT_TEXT_HERE

Passar muito tempo analisando/compilando o código pode atrasar muito o tempo que um usuário leva para interagir com seu site. Quanto mais JavaScript você enviar, mais tempo levará para analisar e compilar antes que o site fique interativo.

Byte por byte, o JavaScript é mais caro para o navegador processar do que a imagem ou a fonte da Web de tamanho equivalente. — Tom Dale

Em comparação com o JavaScript, há muitos custos envolvidos no processamento de imagens de tamanho equivalente (elas ainda precisam ser decodificadas!), mas, em hardwares móveis médios, o JS tem mais probabilidade de afetar negativamente a interatividade de uma página.

ALT_TEXT_HERE
JavaScript e bytes de imagem têm custos muito diferentes. As imagens geralmente não bloqueiam a linha de execução principal nem impedem que as interfaces se tornem interativas enquanto são decodificadas e rastreadas. No entanto, o JS pode atrasar a interatividade devido a custos de análise, compilação e execução.

Quando falamos sobre a lentidão da análise e da compilação, o contexto é importante. Estamos falando de smartphones comuns. Os usuários comuns podem ter smartphones com CPUs e GPUs lentas, sem cache L2/L3 e que podem até mesmo ter memória limitada.

Os recursos da rede e do dispositivo nem sempre são compatíveis. Um usuário com uma conexão Fiber incrível não tem necessariamente a melhor CPU para analisar e avaliar o JavaScript enviado ao dispositivo. Isso também é verdade ao contrário: uma conexão de rede terrível, mas uma CPU extremamente rápida. — Kristofer Baxter, LinkedIn

Confira abaixo o custo de analisar cerca de 1 MB de JavaScript descompactado (simples) em hardwares de baixo e alto desempenho. Há uma diferença de 2 a 5 vezes no tempo de análise/compilação de código entre os smartphones mais rápidos do mercado e os mais lentos.

ALT_TEXT_HERE
Este gráfico destaca os tempos de análise de um pacote de 1 MB de JavaScript (~250 KB compactado) em dispositivos desktop e móveis de diferentes classes. Ao analisar o custo da análise, são os valores descomprimidos a serem considerados.Por exemplo, ~250 KB de JS compactado em GZIP são descompactados para ~1 MB de código.

E um site real, como CNN.com?

No iPhone 8 de última geração, leva cerca de 4 segundos para analisar/compilar o JS da CNN, em comparação com cerca de 13 segundos para um smartphone comum (Moto G4). Isso pode afetar significativamente a rapidez com que um usuário pode interagir totalmente com esse site.

ALT_TEXT_HERE
Acima, vemos os tempos de análise comparando o desempenho do chip A11 Bionic da Apple com o Snapdragon 617 em hardwares Android mais comuns.

Isso destaca a importância de testar em hardware médio (como o Moto G4) em vez de apenas o smartphone que pode estar no seu bolso. No entanto, o contexto é importante: otimize para as condições de dispositivo e rede dos seus usuários.

ALT_TEXT_HERE
O Google Analytics pode fornecer insights sobre as classes de dispositivos móveis que seus usuários reais usam para acessar seu site. Isso pode oferecer oportunidades para entender as restrições reais de CPU/GPU com que eles estão operando.

Estamos enviando muito JavaScript? Talvez sim :)

Usando o HTTP Archive (os 500 mil sites mais acessados) para analisar o estado do JavaScript em dispositivos móveis, podemos verificar que 50% dos sites levam mais de 14 segundos para ficarem interativos. Esses sites gastam até quatro segundos apenas analisando e compilando JS.

ALT_TEXT_HERE

Considere o tempo necessário para buscar e processar JS e outros recursos. Talvez não seja surpreendente que os usuários tenham que esperar um pouco antes de sentir que as páginas estão prontas para uso. Podemos melhorar isso.

Remover JavaScripts não críticos das suas páginas pode reduzir os tempos de transmissão, a análise e a compilação de uso intensivo de CPU e o overhead de memória. Isso também ajuda a tornar suas páginas interativas mais rapidamente.

Tempo de execução

Não são apenas a análise e a compilação que podem ter um custo. A execução do JavaScript (execução do código após ser analisado/compilado) é uma das operações que precisa acontecer na linha de execução principal. Tempos de execução longos também podem atrasar o tempo que um usuário leva para interagir com seu site.

ALT_TEXT_HERE

Se o script for executado por mais de 50 ms, o tempo de interação será atrasado pelo tempo total necessário para fazer o download, compilar e executar o JS. — Alex Russell

Para resolver isso, o JavaScript se beneficia de pequenos pedaços para evitar travar a linha de execução principal. Verifique se é possível reduzir a quantidade de trabalho que está sendo feita durante a execução.

Outros custos

O JavaScript pode afetar a performance da página de outras maneiras:

  • Memória. As páginas podem parecer instáveis ou pausadas com frequência devido à GC (coleta de lixo). Quando um navegador recupera memória, a execução do JS é pausada para que um navegador que colete lixo com frequência possa pausar a execução com mais frequência do que gostamos. Evite vazamentos de memória e pausas frequentes de GC para evitar que as páginas tenham problemas.
  • Durante a execução, o JavaScript de longa duração pode bloquear a linha de execução principal, fazendo com que as páginas não respondam. Dividir o trabalho em partes menores (usando requestAnimationFrame() ou requestIdleCallback() para programação) pode minimizar problemas de capacidade de resposta, o que pode ajudar a melhorar a Interaction to Next Paint (INP).

Padrões para reduzir o custo de entrega do JavaScript

Quando você tenta manter os tempos de análise/compilação e transmissão de rede para JavaScript lentos, há padrões que podem ajudar, como o agrupamento baseado em rota ou PRPL.

PRPL

O PRPL (push, render, pré-cache, lazy-load) é um padrão que otimiza a interatividade com a divisão e o armazenamento em cache agressivos do código:

ALT_TEXT_HERE

Vamos imaginar o impacto que isso pode ter.

Analisamos o tempo de carregamento de sites para dispositivos móveis e apps Web progressivos conhecidos usando as estatísticas de chamadas de execução do V8. Como podemos ver, o tempo de análise (mostrado em laranja) é uma parte significativa do tempo que muitos desses sites passam:

ALT_TEXT_HERE

O Wego, um site que usa PRPL, consegue manter um tempo de análise baixo para as rotas, tornando-se interativo muito rapidamente. Muitos dos outros sites acima adotaram a divisão de código e os orçamentos de performance para tentar reduzir os custos do JS.

Inicialização progressiva

Muitos sites otimizam a visibilidade do conteúdo em detrimento da interatividade. Para conseguir uma primeira pintura rápida quando você tem pacotes JavaScript grandes, os desenvolvedores às vezes usam a renderização no servidor e, em seguida, a "atualizam" para anexar manipuladores de eventos quando o JavaScript finalmente é buscado.

Tenha cuidado, porque isso tem custos. Você 1) geralmente envia uma resposta HTML maior, que pode aumentar nossa interatividade; 2) pode deixar o usuário em um vale estranho, em que metade da experiência não pode ser interativa até que o JavaScript conclua o processamento.

A inicialização progressiva pode ser uma abordagem melhor. Envie uma página minimamente funcional (com apenas o HTML/JS/CSS necessário para a rota atual). À medida que mais recursos chegam, o app pode fazer o carregamento lento e desbloquear mais recursos.

ALT_TEXT_HERE
Inicialização progressiva por Paul Lewis

Carregar o código de forma proporcional ao que está na tela é o objetivo. O PRPL e o Bootstrap progressivo são padrões que podem ajudar a fazer isso.

Conclusões

O tamanho da transmissão é fundamental para redes de baixo custo. O tempo de análise é importante para dispositivos vinculados à CPU. Manter esses valores baixos é importante.

As equipes tiveram sucesso ao adotar orçamentos de desempenho rígidos para manter os tempos de transmissão e análise/compilação do JavaScript baixos. Veja o artigo de Alex Russell "Can You Afford It?: Orçamentos de desempenho da Web no mundo real" para orientações sobre orçamentos para dispositivos móveis.

ALT_TEXT_HERE
É útil considerar quanto espaço "livre" do JS as decisões de arquitetura que tomamos podem deixar para a lógica do app.

Se você estiver criando um site voltado para dispositivos móveis, faça o possível para desenvolver em hardwares representativos, mantenha os tempos de análise/compilação do JavaScript baixos e adote um orçamento de performance para garantir que sua equipe possa monitorar os custos do JavaScript.

Saiba mais