
Reduzir o tempo de execução de JavaScript: enviar menos, depois adiar, depois dividir
- vuetelemetry
- Guias
- 8 min de leitura
O Lighthouse dá o número, mas a correção quase nunca está no seu próprio código. A maior parte do tempo de execução pertence a dependências que correm antes de haver algo visível. Três passos, pela ordem que realmente funciona, e a única medição a fazer primeiro.
O Lighthouse diz-lhe que a execução de JavaScript demorou 3,4 segundos e oferece uma lista de ficheiros. O instinto é abrir o seu próprio código e otimizar ciclos. É quase sempre o sítio errado, e a razão é aritmética: numa aplicação típica, o seu código é uma pequena parte dos bytes que o browser analisa, compila e executa.
O tempo de execução são três custos distintos que as ferramentas mostram como um só. A análise transforma o texto numa árvore sintática abstrata, a compilação transforma-a em bytecode e a avaliação executa-o de facto. Os três crescem com a quantidade de JavaScript que envia, e é por isso que a primeira alavanca não é código mais rápido, é menos código.
Medir primeiro, nunca adivinhar

Meça antes de mexer em alguma coisa. Abra o painel Performance, grave um carregamento de página com o CPU abrandado a 4x e leia a vista Bottom-Up agrupada por URL. Ela atribui os milissegundos aos ficheiros. Um palpite sobre qual dependência é cara falha com frequência suficiente para fazer perder um dia.
Enviar menos, antes de tudo
Primeiro passo: enviar menos. O separador Coverage das DevTools do Chrome mostra que percentagem de cada ficheiro nunca foi executada. Valores acima de 70 por cento por usar são comuns, e normalmente apontam para uma biblioteca importada por causa de uma só função, um pacote de polyfills para browsers que já não suporta, ou uma biblioteca inteira de datas ou ícones onde bastavam três importações.
- Gravar com o CPU abrandado a 4x e ler Bottom-Up por URL antes de mudar alguma coisa
- Separador Coverage: ficheiros com mais de 70 por cento por usar são os primeiros candidatos a retirar
- Substituir uma biblioteca por uma função nativa do browser quando existe, Intl antes de uma biblioteca de datas
- defer para scripts que mexem no DOM, etiquetas de terceiros carregadas depois do evento load
- Dividir por rota só depois de retirar o que não devia ser enviado de todo
A substituição raramente é espetacular. Uma biblioteca de formatação de datas com 70 kB pode muitas vezes ser substituída por `Intl.DateTimeFormat`, que não custa nada porque já está no browser. Essa única substituição elimina de uma vez a análise, a compilação e a avaliação, em vez de as tornar mais rápidas.
Adiar o que a primeira pintura não precisa
Segundo passo: adiar o que a primeira pintura não precisa. Ferramentas de análise, widgets de conversa, banners de consentimento e bibliotecas de carrossel executam todos antes de a página ser utilizável sem lhe acrescentarem nada. `defer` numa etiqueta script mantém a transferência em paralelo e passa a execução para depois da análise; `async` executa-o assim que chega, o que é pior para tudo o que mexe no DOM.
No caso concreto das etiquetas de terceiros, carregá-las depois do evento load, ou na primeira interação, tira todo o seu custo da janela que é medida e, sobretudo, da janela em que o utilizador está à espera.
Dividir o bundle, e por esta ordem
Terceiro passo: dividir o bundle. A divisão do código por rota significa que quem visita a página inicial não analisa o percurso de pagamento. Numa aplicação Vue, `defineAsyncComponent` e o `import()` dinâmico tratam disso, e os bundlers modernos dividem automaticamente por rota quando as rotas são importadas de forma diferida.
A ordem destes três passos conta. Dividir um bundle que contém 200 kB de biblioteca por usar só adia o momento em que paga por ela. Retirar primeiro a biblioteca torna a divisão mais pequena e o ganho maior.
O que o número não diz
Um aviso sobre o próprio número. O Total Blocking Time e o Interaction to Next Paint são o que o visitante sente; o tempo de execução de JavaScript é uma componente deles, não um sinónimo. Reduzir o tempo de execução numa página que já era rápida não muda nada percetível, e uma página pode ter uma má pontuação de execução e parecer fluida porque o trabalho acontece depois da pintura.
Os dados de campo decidem. Um teste de laboratório num portátil rápido esconde precisamente a população que sofre: telemóveis Android de gama média numa rede congestionada, onde uma análise de 300 milissegundos passa a dois segundos. Se os números de campo são bons e os de laboratório feios, acredite no campo.
Antes de otimizar os scripts, vale a pena verificar se os bytes são sequer comprimidos: gzip vs Brotli, e o que encontrámos ao medir 336 organismos públicos.
FAQ
O que causa um tempo de execução de JavaScript elevado?
O volume de JavaScript que o browser tem de analisar, compilar e avaliar, dominado na maioria dos projetos pelas dependências e não pelo código da aplicação. As tarefas longas no thread principal durante o arranque, normalmente a inicialização da framework e as etiquetas de terceiros, são o que transforma esse volume num número que o utilizador sente.
O tempo de execução é o mesmo que o Total Blocking Time?
Não. O tempo de execução é quanto tempo os scripts correm; o Total Blocking Time mede quanto tempo o thread principal esteve bloqueado mais de 50 milissegundos seguidos entre o First Contentful Paint e a interatividade. O tempo de execução é uma componente dele, e trabalho feito depois da pintura pode somar tempo de execução sem somar tempo de bloqueio.
defer ou async reduzem o tempo de execução?
Nenhum dos dois reduz o trabalho; ambos mudam o momento em que acontece. defer executa o script depois da análise do HTML, pela ordem do documento, o que não bloqueia a renderização e é a escolha predefinida certa para tudo o que mexe no DOM. async executa-o assim que é transferido, por uma ordem imprevisível.
Quanto JavaScript é demasiado?
Não há um limiar universal, e qualquer número apresentado como tal é uma regra empírica. O teste útil são os dados de campo: se o Interaction to Next Paint e o Largest Contentful Paint estão dentro dos limiares para utilizadores reais em dispositivos reais, a quantidade que envia é defensável, diga o que disser o número de bytes.
Devo abandonar a minha framework para resolver isto?
Raramente, e não como primeiro passo. Retirar dependências por usar, adiar etiquetas de terceiros e dividir por rota recupera normalmente mais tempo do que uma migração de framework, com uma fração do risco. Pondere a migração só se a medição apontar para a própria inicialização da framework depois desses três passos.



A ordem destes três passos conta. Dividir um bundle que contém 200 kB de biblioteca por usar só adia o momento em que paga por ela. Retirar primeiro a biblioteca torna a divisão mais pequena e o ganho maior.