À 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.
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.
- Use a divisão de código para dividir o JavaScript em elementos essenciais e não essenciais. Os bundlers de módulo como o webpack oferecem suporte ao divisão de código.
- Carregamento lento em código não crítico.
- Minificação
- Use o UglifyJS para minimizar o código ES5.
- Use babel-minify ou uglify-es para minificar o ES2015+.
- Compressão
- Remover o código não utilizado.
- Identifique oportunidades para código que pode ser removido ou carregado de forma lenta com a cobertura de código do DevTools.
- Use babel-preset-env e a lista de navegadores para evitar a transpilação de recursos que já estão em navegadores modernos. Desenvolvedores avançados podem achar que uma análise cuidadosa dos pacotes do webpack ajuda a identificar oportunidades para reduzir dependências desnecessárias.
- Para remover o código, consulte tree-shaking, as otimizações avançadas do Closure Compiler e os plug-ins de redução de biblioteca, como o lodash-babel-plugin ou o ContextReplacementPlugin do webpack para bibliotecas como o Moment.js.
- 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".
As guias "Bottom-Up" e "Call Tree" mostram os tempos de análise/compilação exatos:

Mas por que isso é importante?
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.

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.

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.

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.

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.
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.
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()
ourequestIdleCallback()
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:
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:
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.

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.

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
- Chrome Dev Summit 2017: práticas recomendadas para carregamento moderno
- Performance de inicialização do JavaScript
- Como resolver a crise de performance da Web (em inglês) — Nolan Lawson
- Você tem condições de pagar? Orçamentos de performance do mundo real — Alex Russell
- Como avaliar frameworks e bibliotecas da Web: Kristofer Baxter
- Resultados do experimento da Cloudflare com Brotli para compactação (observe que o Brotli dinâmico com uma qualidade mais alta pode atrasar a renderização inicial da página. Portanto, avalie com cuidado. Você provavelmente quer compactar estaticamente.
- Performance Futures — Sam Saccone