Otimização da inicialização em JavaScript

Addy Osmani
Addy Osmani

À medida que criamos sites mais dependentes de JavaScript, às vezes pagamos pelo que enviamos de maneiras que nem sempre conseguimos ver com facilidade. Neste artigo, abordaremos por que um pouco de disciplina pode ajudar se você quiser que seu site carregue e seja interativo rapidamente em dispositivos móveis. Enviar menos JavaScript pode significar menos tempo na transmissão de rede, menos tempo gasto com descompactação de código e menos tempo para analisar e compilar esse JavaScript.

Quando a maioria dos desenvolvedores pensa no custo do JavaScript, é em termos de custo de download e execução. Quanto mais lenta é a conexão de um usuário, quanto mais lenta é a conexão de um usuário, mais bytes de JavaScript pela rede são enviados.

Quando um navegador solicita um
recurso, esse recurso 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 eficaz que um usuário tem pode não ser 3G, 4G ou Wi-Fi. Você pode estar usando o Wi-Fi de uma cafeteria, mas conectado a um ponto de acesso de rede móvel com velocidades de 2G.

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

  • Envie apenas o código que o usuário precisa.
  • Minimização
  • Compactação
    • No mínimo, use o gzip para compactar recursos baseados em texto.
    • Considere usar o Brotli aproximadamente q11 (links em inglês). O Brotli tem um desempenho melhor que o gzip na taxa de compactação. Isso ajudou a CertSimple a economizar 17% no tamanho de bytes JS compactados e o LinkedIn a economizar 4% nos tempos de carregamento.
  • Como remover códigos não utilizados
  • Como armazenar o código em cache para minimizar as viagens de rede.
    • Use o armazenamento em cache HTTP para garantir que os navegadores armazenem as respostas em cache de maneira eficaz. Determine os tempos de vida ideais 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 resiliente e oferecer acesso antecipado a recursos como o cache de código do V8.
    • Use o armazenamento em cache de longo prazo para não precisar buscar novamente recursos que não foram alterados. Se estiver usando o Webpack, consulte hash do nome de arquivo (link em inglês).

Analisar/compilar

Após o download, um dos custos mais pesados do JavaScript é o momento em que um mecanismo de JS analisa/compila esse código. No Chrome DevTools, a análise e a compilação fazem parte do tempo de "Script" amarelo no painel "Desempenho".

ALT_TEXT_HERE

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

ALT_TEXT_HERE
Painel de desempenho do Chrome DevTools > De baixo para cima. Com as estatísticas de chamada do ambiente de execução do V8 ativadas, podemos conferir o tempo gasto em fases como Analisar e Compilar

Mas, por que isso é importante?

ALT_TEXT_HERE

Passar muito tempo analisando/compilando um código pode atrasar muito a rapidez com que um usuário pode interagir com o site. Quanto mais JavaScript você enviar, mais tempo levará para analisá-lo e compilá-lo antes que seu site fique interativo.

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

Em comparação com o JavaScript, há vários custos envolvidos no processamento de imagens de tamanhos equivalentes (elas ainda precisam ser decodificadas). No entanto, em hardwares de dispositivos móveis comuns, é mais provável que o JS afete negativamente a interatividade de uma página.

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

Quando falamos sobre a lentidão da análise e compilação, porque o contexto é importante, estamos nos referindo à média de smartphones. Os usuários médios podem ter smartphones com CPUs e GPUs lentas, sem cache L2/L3 e com limitações de memória.

Os recursos de rede e do dispositivo nem sempre correspondem. 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, mas uma conexão de rede terrível, mas uma CPU ultrarrápida. — Kristofer Baxter, LinkedIn

Confira abaixo o custo de analisar aproximadamente 1 MB de JavaScript descompactado (simples) em hardware de baixa e alta tecnologia. Há uma diferença de 2 a 5 vezes no tempo para analisar/compilar código entre os smartphones mais rápidos do mercado e os smartphones comuns.

ALT_TEXT_HERE
Este gráfico destaca os tempos de análise de um pacote de 1 MB de JavaScript (aproximadamente 250 KB comprimido com gzip) em dispositivos móveis e computadores de diferentes classes. Ao analisar o custo da análise, é preciso considerar os números descompactados, por exemplo, cerca de 250 KB de JS comprimido com gzip descompacta para aproximadamente 1 MB de código.

Que tal um site real, como o CNN.com?

No iPhone 8 de última geração, leva apenas cerca de 4 segundos para analisar/compilar o JS da CNN, em comparação com aproximadamente 13 segundos em um smartphone médio (Moto G4). Isso pode afetar significativamente a rapidez com que um usuário pode interagir completamente com o 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 um hardware Android mais médio.

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

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

Estamos realmente enviando muito JavaScript? É possível :)

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

ALT_TEXT_HERE

Considere o tempo necessário para buscar e processar o JS e outros recursos, e talvez não seja surpreendente que os usuários precisem esperar um pouco antes de sentir que as páginas estão prontas para uso. Com certeza podemos fazer melhor.

A remoção de JavaScript não essencial das suas páginas pode reduzir os tempos de transmissão, a análise e a compilação que usam muita CPU, além da possível sobrecarga da memória. Isso também ajuda a acelerar a interatividade das suas páginas.

Ambiente de execução

Não é apenas analisar e compilar que pode ter custos. A execução de JavaScript (executando o código depois de analisado/compilado) é uma das operações que precisa acontecer na linha de execução principal. Tempos de execução longos também podem adiar a rapidez com que um usuário pode interagir com seu site.

ALT_TEXT_HERE

Se o script for executado por mais de 50 ms, o tempo até a interação será atrasado por todo o tempo necessário para fazer o download, compilar e executar o JS – Alex Russell

Para resolver isso, o JavaScript tem a vantagem de estar em pequenos pedaços para evitar o bloqueio da linha de execução principal. Descubra se é possível reduzir a quantidade de trabalho realizada durante a execução.

Outros custos

O JavaScript pode afetar o desempenho da página de outras maneiras:

  • Memória. As páginas podem parecer instáveis ou pausar com frequência devido à coleta de lixo (GC, na sigla em inglês). Quando um navegador recupera a memória, a execução do JS é pausada para que um navegador que coleta lixo com frequência possa pausar a execução com uma frequência maior do que a esperada. Evite vazamentos de memória e pausas frequentes de GCs para manter as páginas livres de instabilidade.
  • Durante o tempo de execução, o JavaScript de longa duração pode bloquear a linha de execução principal, causando páginas que não respondem. Dividir o trabalho em partes menores (usando requestAnimationFrame() ou requestIdleCallback() para a programação) pode minimizar problemas de capacidade de resposta, o que pode ajudar a melhorar a Interação com a próxima exibição (INP, na sigla em inglês).

Padrões para reduzir o custo de entrega de JavaScript

Quando você está tentando 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 com base em rota ou o PRPL (link em inglês).

PRPL (em inglês)

PRPL (Push, Render, Pre-cache, Lazy-load) é um padrão que otimiza a interatividade por meio de armazenamento em cache e divisão de código agressivos:

ALT_TEXT_HERE

Vamos conferir seu impacto.

Analisamos o tempo de carregamento de sites para dispositivos móveis populares e Progressive Web Apps usando as Estatísticas de chamada do tempo de execução do V8. Como podemos ver, o tempo de análise (mostrado em laranja) é uma parte significativa do tempo gasto por muitos desses sites:

ALT_TEXT_HERE

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

Bootstrap progressivo

Muitos sites otimizam a visibilidade do conteúdo em detrimento da interatividade. Para ter uma primeira exibição rápida quando há grandes pacotes JavaScript, os desenvolvedores às vezes empregam a renderização no servidor. Em seguida, fazem "upgrade" para anexar gerenciadores de eventos quando o JavaScript finalmente é buscado.

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

O bootstrap progressivo pode ser uma abordagem melhor. Envie uma página minimamente funcional, composta apenas pelo HTML/JS/CSS necessário para a rota atual. Conforme mais recursos chegam, o app pode carregar lentamente e desbloquear mais funcionalidades.

ALT_TEXT_HERE
Bootstrap progressivo por Paul Lewis

Carregar o código proporcionalmente ao que está sendo visualizado é o segredo. PRPL e bootstrap progressivo são padrões que podem ajudar a fazer isso.

Conclusões

O tamanho da transmissão é fundamental para redes de baixa capacidade. O tempo de análise é importante para dispositivos vinculados à CPU. É importante mantê-los baixos.

Equipes tiveram sucesso na adoção de orçamentos de desempenho rigorosos para manter os tempos de transmissão e análise/compilação de JavaScript baixos. Confira "Can You Afford It?: reais de desempenho da Web" e orientações sobre orçamentos para dispositivos móveis.

ALT_TEXT_HERE
É útil considerar o espaço livre de JS que as decisões arquitetônicas que tomamos nos deixam para a lógica do app.

Se você estiver criando um site destinado a dispositivos móveis, faça seu melhor para desenvolver em hardware representativo, mantenha os tempos de análise/compilação do JavaScript baixos e adote um orçamento de desempenho para garantir que sua equipe possa acompanhar os custos de JavaScript.

Saiba mais