À medida que criamos sites que dependem mais do JavaScript, às vezes pagamos pelo que enviamos de maneiras que nem sempre são fáceis de ver. 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. Enviar menos JavaScript pode significar menos tempo na transmissão de rede, menos tempo gasto na descompactação do código e menos tempo na análise e compilação desse 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 efetiva do usuário pode não ser 3G, 4G ou Wi-Fi. Você pode estar no Wi-Fi de uma cafeteria, mas conectado a um ponto de acesso celular com velocidades 2G.
É possível reduzir o custo de transferência de rede do JavaScript:
- Enviar apenas o código que o usuário precisa.
- Use a divisão de código para separar o JavaScript em essencial e não essencial. Os bundlers de módulo, como o webpack, são compatíveis com a divisão de código.
- Carregamento lento de código não crítico.
- Minificação
- Use o UglifyJS para minificar o código ES5.
- Use babel-minify ou uglify-es para minificar o ES2015+.
- Compactação
- Remover código não utilizado.
- Identifique oportunidades de código que podem ser removidas ou carregadas lentamente com a cobertura de código do DevTools.
- Use babel-preset-env e browserlist para evitar a transpilação de recursos que já estão em navegadores mais recentes. Os 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 código, consulte tree-shaking, as otimizações avançadas do Closure Compiler e plug-ins de corte de bibliotecas, como lodash-babel-plugin ou ContextReplacementPlugin do webpack para bibliotecas como Moment.js.
- Armazenar 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 dar 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 foram alterados. Se estiver usando o Webpack, consulte hashing de nome de arquivo.
Analisar/compilar
Depois do download, um dos custos mais pesados do JavaScript é o tempo que um mecanismo de 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 exatos de análise/compilação:
Mas por que isso é importante?

Passar muito tempo analisando/compilando 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 se torne 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 tamanho equivalente (elas ainda precisam ser decodificadas!), mas, em um hardware móvel comum, o JS tem mais chances de afetar negativamente a interatividade de uma página.
Quando dizemos que a análise e a compilação são lentas, 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é ter restrições de memória.
Os recursos de rede e de dispositivo nem sempre são os mesmos. Um usuário com uma conexão de fibra incrível não tem necessariamente a melhor CPU para analisar e avaliar o JavaScript enviado ao dispositivo. O mesmo vale para o inverso: uma conexão de rede terrível, mas uma CPU extremamente rápida. — Kristofer Baxter, LinkedIn
Abaixo, vemos o custo de analisar cerca de 1 MB de JavaScript descompactado (simples) em hardware 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 smartphones comuns.
E um site real, como o CNN.com?
No iPhone 8, um dispositivo sofisticado, leva apenas cerca de 4 segundos para analisar/compilar o JS da CNN, em comparação com cerca de 13 segundos em um smartphone comum (Moto G4). Isso pode afetar significativamente a rapidez com que um usuário pode interagir totalmente com o site.
Isso destaca a importância de testar em hardware médio (como o Moto G4) em vez de apenas no smartphone que você tem no bolso. No entanto, o contexto é importante: otimize para as condições de dispositivo e rede dos usuários.
Estamos mesmo enviando JavaScript demais? Err, possibly :)
Usando o HTTP Archive (os 500 mil principais 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.

Considerando o tempo necessário para buscar e processar o JS e outros recursos, não é surpresa que os usuários tenham que esperar um pouco antes de sentir que as páginas estão prontas para uso. Com certeza podemos melhorar isso.
Remover o JavaScript não crítico das suas páginas pode reduzir os tempos de transmissão, a análise e a compilação que consomem muita CPU e a possível sobrecarga 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 de JavaScript (executar 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 atrasar o momento em que um usuário pode interagir com seu site.

Se o script for executado por mais de 50 ms, o tempo até a interatividade será atrasado pelo tempo total necessário para baixar, compilar e executar o JS — Alex Russell
Para resolver isso, o JavaScript se beneficia de estar em pequenos blocos para evitar o bloqueio da linha de execução principal. Verifique se é possível reduzir a quantidade de trabalho que está sendo 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 à GC (coleta de lixo). Quando um navegador recupera memória, a execução do JS é pausada. Portanto, um navegador que coleta lixo com frequência pode pausar a execução com mais frequência do que gostaríamos. Evite vazamentos de memória e pausas frequentes de GC para manter as páginas sem instabilidade.
- Durante o tempo de 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 responsividade, o que ajuda 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 a divisão em blocos com base em rotas ou PRPL.
PRPL
PRPL (Push, Render, Pré-armazenamento em cache, Lazy-load) é um padrão que otimiza a interatividade por meio de divisão de código e armazenamento em cache agressivos:

Vamos visualizar o impacto que isso pode ter.
Analisamos o tempo de carregamento de sites para dispositivos móveis e apps Web progressivos usando as estatísticas de chamada de ambiente 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:

O Wego, um site que usa o 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 montantes de desempenho para tentar reduzir os custos de JS.
Bootstrapping progressivo
Muitos sites otimizam a visibilidade do conteúdo em detrimento da interatividade. Para conseguir uma primeira exibição rápida quando você tem grandes pacotes de JavaScript, os desenvolvedores às vezes usam a renderização do lado do servidor e, em seguida, fazem um "upgrade" para anexar manipuladores de eventos quando o JavaScript é finalmente buscado.
Cuidado: isso tem custos próprios. Você 1) geralmente envia uma resposta HTML maior, que pode prejudicar a interatividade, e 2) pode deixar o usuário em um vale da estranheza, em que metade da experiência não pode ser interativa até que o JavaScript termine o processamento.
A inicialização progressiva pode ser uma abordagem melhor. Envie uma página minimamente funcional (composta apenas pelo 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 proporcionalmente ao que está em exibição é o ideal. O PRPL e a inicialização progressiva são padrões que podem ajudar a fazer isso.
Conclusões
O tamanho da transmissão é fundamental para redes de baixa qualidade. O tempo de análise é importante para dispositivos vinculados à CPU. É importante manter esses valores baixos.
As equipes têm tido sucesso ao adotar montantes de desempenho rígidos para manter baixos os tempos de transmissão e análise/tempo de compilação do JavaScript. Consulte o artigo de Alex Russell "Can You Afford It?: Real-world Web Performance Budgets" para orientações sobre montantes para dispositivos móveis.
Se você estiver criando um site para dispositivos móveis, faça o possível para desenvolver em hardware representativo, mantenha os tempos de análise/compilação do JavaScript baixos e adote um montante de desempenho para garantir que sua equipe possa ficar de olho nos custos do JavaScript.
Saiba mais
- Chrome Dev Summit 2017 - Práticas recomendadas de carregamento moderno
- Desempenho de inicialização do JavaScript
- Como resolver a crise de desempenho da Web — Nolan Lawson
- Você pode pagar? Montantes de desempenho reais — Alex Russell
- Como avaliar frameworks e bibliotecas da Web — Kristofer Baxter
- Resultados da Cloudflare ao testar o Brotli para compactação (o Brotli dinâmico com uma qualidade mais alta pode atrasar a renderização inicial da página, então avalie com cuidado. Provavelmente, você quer fazer a compactação estática.
- Performance Futures — Sam Saccone