Missed the action at the 2018 Chrome Dev Summit? Catch up with our playlist on the Google Chrome Developers channel on YouTube. Watch now.

Otimização da inicialização em JavaScript

Ao criar sites que dependem fortemente de JavaScript, às vezes pagamos pelo que enviamos de maneiras que nem sempre visualizamos com facilidade. Neste artigo, abordaremos por que um pouco de disciplina pode ser útil se você quiser que o site carregue e seja interativo com rapidez em dispositivos móveis. Oferecer menos JavaScript significa menos tempo para transmissão de rede, menos tempo gasto para descompactar códigos e menos tempo para analisar e compilar esse JavaScript.

Rede

Quando a maioria dos desenvolvedores pensa no custo de JavaScript, é em termos de custo de download e execução. Quanto mais lenta for a conexão de um usuário, mais tempo demorará para enviar mais bytes de JavaScript pela rede.

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

Isso pode ser um problema, até mesmo em países de primeiro mundo, já que o tipo de conexão de rede eficaz que um usuário tem talvez não seja 3G, 4G ou Wi-Fi. O usuário pode estar usando uma rede Wi-Fi pública, mas na realidade estar 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 com estas opções:

  • Somente enviar o código que um usuário precisa.
  • Minificação
  • Compressão
    • No mínimo, use gzip para compactar recursos baseados em texto.
    • Considere usar Brotli ~q11. O Brotli tem melhor desempenho em taxas de compressão do que o gzip. Ele ajudou a CertSimple a economizar 17% no tamanho de bytes de JS compactado e a LinkedIn a economizar 4% nos tempos de carregamento.
  • Remover código não utilizado.
  • Armazenar código em cache para reduzir as viagens da rede.
    • Use HTTP o armazenamento em cache para garantir que os navegadores armazenem as respostas em cache de maneira eficaz. Determine o ciclo de vida 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 seu app resiliente e oferecer acesso ávido a recursos como o cache de código do V8.
    • Use o armazenamento em cache de longo prazo para não precisar recuperar novamente recursos que não mudaram. Se for usar o Webpack, consulte o hash de nome de arquivo.

Analisar/compilar

Após o download, um dos custos mais pesados de JavaScript é quando 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 “Performance”.

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

Painel “Performance” > “Bottom-Up” do Chrome DevTools. Com a opção “Runtime Call Stats” do V8 ativada, podemos ver o tempo gasto nas fases Parse e Compile

Mas, por que isso é importante?

Passar muito tempo analisando/compilando um código pode atrasar muito a rapidez com a qual um usuário pode interagir com o site. Quanto mais você enviar JavaScript, mais demorado será para analisar e compilá-lo antes de o site ficar interativo.

Byte por byte, o JavaScript é mais caro para o navegador processar do que imagens ou fontes da Web de mesmo tamanho — Tom Dale

Em comparação ao JavaScript, há inúmeros custos envolvidos no processamento de imagens de mesmo tamanho (elas ainda precisam ser decodificadas!), mas, no hardware móvel médio, é mais provável que o JS afete negativamente a interatividade da página.

Bytes de JavaScript e imagem possuem custos muito diferentes. As imagens geralmente não bloqueiam o thread principal nem impedem que as interfaces fiquem interativas enquanto são decodificadas e rasterizadas. No entanto, o JS pode atrasar a interatividade por causa da análise, da compilação e dos custos de execução.

Quando falamos sobre a lentidão da análise e compilação, estamos falando sobre o smartphones medianos aqui. É importante levar esse contexto em consideração. Usuários médios podem ter smartphones com CPUs e GPUs lentas, sem cache L2/L3 e inclusive com memória limitada.

Nem sempre os recursos de rede e do dispositivo se equiparam. Um usuário com uma conexão Fiber incrível não necessariamente tem a melhor CPU para analisar e avaliar o JavaScript enviado para seu dispositivo. O inverso também é válido, uma conexão de rede terrível, mas uma CPU megarrápida. — Kristofer Baxter, LinkedIn

Abaixo, vemos o custo de analisar aproximadamente 1 MB de JavaScript descompactado (simples) em um hardware de baixa e alta capacidade. Há uma diferença de 2 a 5x no tempo para analisar/compilar código entre os smartphones mais rápidos do mercado e os smartphones medianos.

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

O que ocorre com um site real, tipo CNN.com?

No iPhone 8 de alta capacidade, leva apenas cerca de 4 segundos para analisar/compilar o JS da CNN em comparação a cerca de 13 segundos em um smartphone mediano (Moto G4). Isso pode afetar significativamente a rapidez com a qual um usuário pode interagir completamente com esse site.

Acima, vemos os tempos de análise comparando o desempenho do chip A11 Bionic da Apple ao Snapdragon 617 no hardware mais mediano de um dispositivo Android.

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

O Google Analytics pode oferecer insights sobre as classes de dispositivos móveis que usuários reais usam para acessar seu site. Isso pode oferecer oportunidades para entender as limitações reais de CPU/GPU que os usuários possuem.

Será que realmente estamos enviando JavaScript demais? Sim, é possível :)

Ao usar o HTTP Archive (os principais 500 mil sites) para analisar o estado de JavaScript em dispositivos móveis, vemos que 50% dos sites levam mais de 14 segundos para ficarem interativos. Esses sites gastam até 4 segundos apenas para analisar e compilar JS.

Leve em consideração o tempo que leva para recuperar e processar o JS e outros recursos e talvez não seja surpreendente que usuários aguardem um pouco até acharem que páginas estão prontas para serem usadas. Certamente podemos melhorar isso.

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

Tempo de execução

A análise e a compilação não são as únicas a terem um custo. A execução de JavaScript (executar o código após ser analisado/compilado) é uma das operações que precisa ocorrer no thread principal. Tempos de execução longos também podem atrasar a rapidez com a qual um usuário pode interagir com o site.

Se o script for executado por mais de 50 ms, o tempo de interação é atrasado pela quantia total de tempo que leva para fazer o download, compilar e executar o JS — Alex Russell

Para resolver esse problema, é bom ter o JavaScript em pequenos pedaços para evitar o bloqueio do thread principal. Descubra se você pode reduzir a quantidade de trabalho feito durante a execução.

Outros custos

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

  • Memória. As páginas podem ficar instáveis ou pausar frequentemente devido à GC (coleta de lixo). Quando um navegador recupera a memória, a execução de 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 desejada. Evite vazamentos de memória e pausas frequentes de GC para ter páginas sem instabilidade.
  • Durante o tempo de execução, o JavaScript de longa duração pode bloquear o thread principal, resultando em páginas não responsivas. Quebrar o trabalho em pedaços menores (usando requestAnimationFrame() ou requestIdleCallback() para agendamento) pode minimizar problemas de responsividade.

Padrões para reduzir o custo de entrega de JavaScript

Quando se está tentando manter os tempos de análise/compilação e transmissão da rede para JavaScript lentos, há padrões que podem ajudar, como agrupamento baseado em rotas ou PRPL.

PRPL

PRPL (sigla para push, renderizar, pré-armazenar em cache e carregar lentamente) é um padrão que otimiza a interatividade pelo armazenamento em cache e pela divisão de código agressivos:

Vamos conferir seu impacto.

Analisamos o tempo de carregamento de sites conhecidos para dispositivos móveis e Progressive Web Apps usando o Runtime Call Stats do V8. Como podemos ver, o tempo de análise (mostrado em laranja) é uma porção significativa do tempo gasto por muitos destes sites:

Wego, um site que usa PRPL, consegue manter um tempo de análise baixo para suas rotas, obtendo interatividade 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 de JS.

Bootstrap progressivo

Muitos sites otimizam a visibilidade do conteúdo em detrimento da interatividade. Para ter uma primeira gravação rápida com pacotes grandes de JavaScript, os desenvolvedores às vezes empregam renderização no servidor. Depois disso, eles fazem "upgrade" para anexar gerenciadores de evento quando o JavaScript finalmente for recuperado.

Mas, cuidado, pois isso tem um preço. Você 1) geralmente envia uma resposta HTML maior, que pode impulsionar a interatividade; 2) pode deixar o usuário isolado de modo que metade da experiência não fique realmente interativa até que o JavaScript termine de ser processado.

O bootstrap progressivo pode ser uma abordagem mais adequada. Envie uma página minimamente funcional (composta apenas pelo HTML/JS/CSS necessário para a rota atual). À medida que mais recursos chegarem, o app poderá carregar lentamente e desbloquear mais recursos.

Bootstrap progressivo por Paul Lewis

Carregar o código em proporção ao que está sendo visualizado é o principal objetivo. PRPL e Bootsrap progressivo são padrões que podem ajudar a fazer isso.

Conclusões

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

Equipes foram bem-sucedidas ao adotar orçamentos de desempenho rigorosos para manter os tempos de transmissão, análise e compilação de JavaScript baixos. Confira "Can You Afford It?: Real-world Web Performance Budgets (Dá para pagar? Os orçamentos de desempenho do mundo real)" de Alex Russel para ver orientações sobre orçamentos para dispositivos móveis.

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

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

Saiba mais