Node.js 25.4: require(esm) e o compile cache de módulos agora são estáveis

O Node.js 25.4.0 saiu em 19 de janeiro na linha Current. À primeira vista, a lista de mudanças parece rotina: atualização de dependências, novos colaboradores, alguns utilitários. Mas um bloco de commits merece atenção: vários recursos que estavam há tempos marcados como experimentais ou em fase de teste foram declarados estáveis de uma vez. O principal deles é o require(esm), e isso fecha um capítulo longo da transição do ecossistema para ES Modules.

O que virou estável

A versão marca como estáveis:

  • require(esm): carregar um módulo ES com require() a partir de código CommonJS;
  • o compile cache de módulos;
  • --heapsnapshot-near-heap-limit;
  • --build-snapshot e --build-snapshot-config;
  • v8.queryObjects();
  • crypto.hash, cuja documentação passou a marcá-lo como estável.

Junto disso, a versão adiciona as flags --require-module e --no-require-module. Elas são novas, não uma mudança de estabilidade, e dão controle explícito sobre o comportamento de require(esm).

Vale notar quem assina boa parte desses commits: Joyee Cheung aparece como autora da estabilização de require(esm), do compile cache, de --build-snapshot, de v8.queryObjects() e das novas flags. Não é uma coleção aleatória de mudanças, e sim um esforço coordenado para tirar do rótulo de experimental recursos que muita gente já usava em produção. Para quem decide o que adotar, “estável” é exatamente o sinal que faltava.

Por que require(esm) estável importa

Durante anos, quem publicava bibliotecas tinha um dilema. Publicar só ESM significava quebrar usuários presos a CommonJS, já que require() não carregava módulos ES. Publicar os dois formatos (o famoso “dual package”) significava manter builds duplicados e lidar com problemas de instâncias duplicadas.

Com require(esm) estável, o caminho fica mais simples: um pacote pode ser só ESM e ainda ser consumido por código CommonJS via require(). Na prática, isso tira de muitos mantenedores a desculpa para continuar publicando CommonJS.

// código CommonJS carregando um pacote que só publica ESM
const lib = require("pacote-somente-esm");

O cuidado é que isso vale para a linha Current. Quem está em uma LTS precisa verificar o estado do recurso na sua versão antes de assumir o mesmo comportamento, e bibliotecas que querem suportar versões antigas do Node ainda vão precisar de cautela.

Desempenho e diagnóstico

Os outros itens estáveis são menos chamativos, mas úteis em produção:

  • Compile cache: guardar em disco o resultado da compilação de módulos acelera inicializações repetidas, o que faz diferença em CLIs e em ambientes serverless com cold start frequente. Estável significa que dá para adotar sem medo de a API mudar.
  • --build-snapshot: gerar um snapshot de inicialização da aplicação é outra arma contra tempo de startup.
  • --heapsnapshot-near-heap-limit e v8.queryObjects(): ferramentas de diagnóstico de memória. A primeira gera um heap snapshot quando o processo se aproxima do limite de heap, que é justamente o momento que você quer capturar ao investigar um vazamento.

Outras novidades

Alguns itens SEMVER-MINOR que vale conhecer:

  • Subpath imports com #/: agora são permitidos imports internos que começam com #/, um padrão comum para apontar para a raiz do projeto via campo imports do package.json.
  • http.setGlobalProxyFromEnv(): nova função para configurar um proxy global a partir de variáveis de ambiente.
  • util.convertProcessSignalToExitCode: novo utilitário para converter sinais de processo em códigos de saída.
  • events.listenerCount() passa a aceitar EventTargets.
  • queueMicrotask preserva o AsyncLocalStorage apenas quando necessário.

Nas dependências, os certificados raiz foram atualizados para o NSS 3.117, o npm foi para a versão 11.7.0 e o SQLite embutido para a 3.51.1. A documentação também passou a tratar os hooks síncronos de módulo como release candidate.

Minha leitura

O Node 25.4 é uma daquelas versões que parecem pequenas e são importantes. Estabilizar require(esm) é o tipo de mudança que, aos poucos, simplifica o ecossistema inteiro: menos builds duplos, menos configurações de exports complicadas, menos erros do tipo ERR_REQUIRE_ESM em projetos legados. Se eu mantivesse uma biblioteca hoje, começaria a planejar uma versão só ESM, sabendo que a adoção completa depende de os usuários chegarem a versões do Node com esse recurso. O suporte a #/ também é bem-vindo: é um padrão simples que dispensa aliases configurados em cada ferramenta.

Fontes