7 MIN LEITURA · Pedro Thomaz

O custo de desempenho das frameworks JavaScript que nunca entra na fatura

O verdadeiro custo de desempenho de uma framework JavaScript não é o download — é o tempo de parsing em telemóveis baratos, a hidratação, a energia e anos de churn. Eis como o medimos.

O custo de desempenho das frameworks JavaScript que nunca entra na fatura

O custo de desempenho das frameworks JavaScript que afunda um projeto quase nunca é o que aparece no selo do tamanho do bundle. Os 45 kB comprimidos são a parte mais barata, mais visível e menos importante da conta. As partes caras são o tempo de parsing e execução num Android com quatro anos, a taxa de hidratação que pagas em cada navegação, a energia que um milhão desses carregamentos queima, e o churn de manutenção que come um dia por trimestre para sempre. Esses custos não aparecem em nenhum benchmark do README de uma framework, por isso quase ninguém os contabiliza.

Nós contabilizamos, porque entregamos PHP renderizado no servidor sem passo de build e mesmo assim temos de justificar, por escrito, cada kilobyte de JavaScript de cliente que adicionamos. Esta é a contabilidade que usamos.

A versão curta: o que significa mesmo "custo de desempenho de uma framework"

Quando se diz que uma framework é "rápida", normalmente quer dizer uma de duas coisas: renderiza depressa numa demo de dev-server num MacBook, ou o seu bundle comprimido é pequeno. Nenhuma é o custo que importa a um visitante real num telemóvel real. O custo verdadeiro são cinco rubricas:

Podes ter um bundle minúsculo e na mesma falhar todos os outros. O peso de transferência é o único custo trivial de resolver com um CDN e aquele com que toda a gente se obceca; os outros quatro são onde os projetos apodrecem em silêncio.

1. O peso do bundle é um engodo

A Cloudflare está à frente de todos os sites que mantemos, por isso um bundle de 200 kB de JavaScript comprime, faz cache na edge e chega em algumas dezenas de milissegundos para a maioria dos visitantes. Se a transferência fosse a história toda, o peso da framework seria um erro de arredondamento.

Não é, porque a contagem de bytes e o custo desses bytes são moedas diferentes. Uma imagem de 100 kB e 100 kB de JavaScript não são equivalentes: o browser descodifica a imagem fora da main thread e acabou. O JavaScript tem de ser parseado, compilado e executado na main thread — a mesma thread que trata de toques, scrolls e renderização. Bytes de script são os bytes mais caros que podes enviar, e o selo do bundle cobra-os como se fossem os mais baratos.

2. O tempo de parsing e execução é medido no dispositivo errado

Eis a rubrica que se salta. Parsear e compilar JavaScript é trabalho de CPU, e a CPU é o recurso que mais varia entre os dispositivos que de facto carregam o teu site. Um visitante português mediano não está no portátil da série M onde a framework foi demonstrada; uma fatia significativa está num Android abaixo dos €200 com dois ou três anos, onde o motor de JS corre a uma fração da velocidade.

Os mesmos 300 kB de framework + código de aplicação que parseiam em ~50 ms numa máquina de programador podem levar várias centenas de milissegundos a mais de um segundo num telemóvel de gama baixa, e esse tempo é gasto na main thread antes de a página estar interativa. É esta a diferença entre um resultado de Lighthouse corrido no teu portátil e os dados de campo no Chrome User Experience Report. Já vimos mais do que uma vez um "95 no laboratório" transformar-se numa careta sob throttling em dispositivo real.

A nossa regra: perfilar o trabalho pesado em JavaScript no Chrome DevTools com a CPU em throttling de 4x ou 6x, num perfil de rede Slow 4G. Se não estiver confortável aí, não está terminado. O número do MacBook é marketing; o número com throttling é a verdade.

3. Hidratação é pagar duas vezes pelo mesmo HTML

Hidratação é o passo em que uma framework de cliente pega no HTML renderizado no servidor e lhe acopla os seus próprios event listeners e estado para a página ficar interativa. A definição importa porque o custo é invisível num screenshot: a página parece pronta enquanto a framework percorre toda a árvore do DOM, reconstrói a sua representação virtual e volta a correr lógica de componentes que o servidor já correu.

Numa página com muito conteúdo isto é desperdício puro. O servidor produziu HTML correto e completo; a função da hidratação é essencialmente tornar um documento estático clicável, e cobra-te o custo total de renderização uma segunda vez no cliente. Para um site de marketing, um caso de estudo, um artigo de journal — a maior parte do que um estúdio como o nosso constrói — não há genuinamente nada para hidratar. O HTML é o produto.

É por isto que o nosso default para conteúdo é não ter qualquer framework de cliente. HTML renderizado no servidor, um CDN, e alguns kilobytes de JavaScript vanilla escrito à mão para as duas ou três coisas que genuinamente precisam dele (um toggle de menu, o beacon de analytics, o lazy-loading de um embed Matterport). Custo zero de hidratação porque não há nada para hidratar. Quando um projeto precisa mesmo de estado rico no cliente — um configurador, a UI de recomendação que construímos para a Jofit — recorremos a ilhas: enviar interatividade apenas para os componentes que dela precisam, deixando o resto como HTML estático.

4. O custo que ninguém fatura: energia e carbono

Cada milissegundo de parsing e execução é um dispositivo algalgures a queimar eletricidade. Um carregamento de página é negligenciável. O tráfego ao longo da vida de um site não é. Se uma framework acrescenta 400 ms de trabalho de CPU por carregamento e uma página é servida alguns milhões de vezes por ano, estás a gastar joules reais em trabalho que o servidor podia ter feito uma só vez.

Isto não é abstrato para nós — temos uma disciplina de sustentabilidade e uma montagem de analytics sem cookies precisamente para podermos raciocinar sobre isto. A matemática do carbono é simples e incómoda: o JavaScript de cliente desloca a computação de um servidor bem gerido para milhares de dispositivos sem controlo, e não podes otimizar hardware que não te pertence. O pedido mais verde é o que chega como HTML acabado e pede ao dispositivo apenas que o pinte. Enviar menos script é a alavanca de carbono web mais eficaz que temos, e calha ser a mesma alavanca que ajuda os Core Web Vitals.

5. O churn de manutenção é um encargo recorrente

O custo que compõe é aquele a que aderes no dia um e pagas todos os trimestres a seguir. Uma framework moderna arrasta uma árvore de dependências de centenas de pacotes transitivos, uma toolchain de build, e uma cadência de versões major que não quer saber do teu calendário de releases. Adota-a e comprometeste-te com migrações, avisos de deprecação, breaking changes em ferramentas que não escolheste, e um node_modules que tens de continuar a auditar à procura de vulnerabilidades.

A nossa stack de PHP e vanilla tem outro modo de falha e somos honestos quanto a isso: escrevemos mais código à mão e não recebemos de graça o ecossistema de componentes de uma framework. A troca que fizemos é que a plataforma por baixo — a linguagem, as APIs do browser — avança num horizonte de cinco a dez anos, não de seis meses. O artigo de journal que estás a ler vai renderizar corretamente com zero manutenção durante anos. A dependência mais barata de manter é a que nunca acrescentaste.

Como isto se liga aos Core Web Vitals

Os cinco custos acima mapeiam quase um para um nos Core Web Vitals da Google, as métricas de campo que medem a experiência real do utilizador:

O nosso objetivo é um resultado de Lighthouse de 95 ou mais e — mais importante — passar nos dados de campo do CrUX, não apenas uma corrida verde no laboratório. Conseguimo-lo tratando o JavaScript de cliente como uma responsabilidade a justificar e não como um default a assumir. A maior parte das páginas que entregamos carrega kilobytes de script de um só dígito. A framework mais rápida é o código que não enviaste.

Então quando recorremos a uma framework?

Isto não é niilismo de frameworks. Uma aplicação complexa e com estado — um dashboard, um editor, uma ferramenta em tempo real — é exatamente para o que as frameworks de cliente foram feitas, e reimplementar uma à mão em vanilla JS é o seu próprio erro caro. A questão é fazer a escolha com base em evidência, não em hábito.

A pergunta que fazemos em cada projeto é simplesmente: qual é o orçamento de interatividade, e esta página gasta-o? O orçamento de uma página de conteúdo é quase zero, por isso recebe quase zero JavaScript. O orçamento de uma aplicação é real, por isso gastamo-lo deliberadamente — e mesmo aí apoiamo-nos em ilhas para que a framework só corra onde o estado de facto vive.

Contabiliza todas as cinco rubricas antes de te comprometeres, não só a do selo. O tamanho do bundle é o preço de etiqueta. Tempo de parsing, hidratação, energia e churn são o custo total de propriedade — e num telemóvel real, no terreno, ao longo da vida de um projeto, é essa a conta que de facto chega.