A Cloudflare reescreveu o registro de módulos no componente de código aberto workerd, o componente principal do ambiente de execução do Workers, com o objetivo de melhorar a velocidade, a conformidade com os padrões e aproximar a forma de carregamento e resolução dos módulos do comportamento do Node.js. Isso ocorre simultaneamente à ativação, por padrão, das APIs estáveis do Node.js no Workers e à disponibilização da publicação de aplicativos de até 64 MiB em todos os planos, após a remoção do limite de tamanho do pacote compactado.
Mas a mudança mais importante não diz respeito apenas ao número de APIs disponíveis. Os aplicativos Node.js também dependem da forma como o ambiente de execução identifica, carrega e armazena módulos em cache. Isso inclui módulos ESM, CommonJS e WebAssembly, responsabilidades assumidas pelo registro de módulos dentro do workerd.
O que mudou no novo registro?
Os desenvolvedores podem experimentar a nova implementação adicionando a flag new_module_registry às configurações do Worker. Quando ativada, o Workers oferece suporte a import.meta.url, import.meta.main e import.meta.resolve(), além de tratar os especificadores de módulos como URLs reais, incluindo strings de consulta e fragmentos de URL.
Na prática, isso significa que as importações relativas seguem as mesmas regras de new URL(), e URLs completas podem ser usadas como especificadores de módulos. Módulos que diferem na string de consulta ou no fragmento posterior da URL também passam a ser módulos independentes, mesmo que apontem para a mesma origem. Assim, duas versões do mesmo arquivo podem ter estados superiores separados, enquanto reutilizar o mesmo especificador retorna a mesma instância do módulo.
O registro também oferece validação correta dos atributos de importação. O tipo json está disponível atualmente, enquanto tipos como text e bytes são definidos, mas rejeitados com uma mensagem clara porque ainda não foram ativados. Atributos desconhecidos também não são mais ignorados silenciosamente: eles resultam em um erro explícito.
Maior compatibilidade com o Node.js
Ao carregar um módulo ESM, require() segue as regras do Node.js para require(esm). Se o módulo exportar um valor chamado module.exports, esse valor será retornado; caso contrário, a chamada retornará um objeto de namespace do módulo. A exceção são os componentes node: integrados ao workerd, que retornam a interface CommonJS esperada em vez de obrigar o desenvolvedor a acessar uma propriedade default.
Há uma limitação importante: require() não pode carregar um módulo que contenha, ou dependa de um módulo que contenha, top-level await, pois require precisa retornar o resultado de forma síncrona. Nesse caso, o Workers lança um erro, e o import() assíncrono é o caminho apropriado. Essa regra continua valendo mesmo que o próprio módulo tenha sido carregado anteriormente por meio de import().
A nova versão também uniformiza as classes de erro e a formulação de suas mensagens, independentemente do método de carregamento, seja por importação estática, import() dinâmico ou require(). A falha ao encontrar o módulo retorna um erro comum, enquanto um especificador que não pode ser analisado como URL resulta em um TypeError. Essa consistência é útil para desenvolvedores que criam carregadores personalizados ou lógica de repetição.
O que muda na prática para os desenvolvedores?
As ferramentas do Workers, como o Wrangler, costumavam reunir a maioria dos arquivos e dependências do aplicativo em um único módulo usando o esbuild, reduzindo o tamanho do grafo processado pelo ambiente de execução. Já o uso do Cloudflare Vite plugin depende, no Vite 8, do Rolldown para produzir um módulo de entrada e módulos adicionais ao dividir o código, como os módulos carregados dinamicamente.
O novo registro abre espaço para que as ferramentas de compilação realizem menos transformações e dependam mais do ambiente de execução para resolver módulos. Isso é especialmente importante ao publicar aplicativos como vários módulos, usar a opção --no-bundle ou lidar com arquivos Wasm, de texto e binários como arquivos independentes, em vez de incorporá-los a um único pacote.
A nova implementação também adia a compilação até que o módulo seja importado pela primeira vez, seja por importação estática ou dinâmica, e permite compartilhar caches de código entre várias instâncias do V8 isolate que executam o mesmo Worker. Segundo a Cloudflare, isso resolve parte do problema da compilação repetida e da existência de várias cópias da origem na memória na implementação anterior.
Limitações e pontos de atenção
Apesar dessas mudanças, o novo registro não será ativado automaticamente para nenhum Worker, antigo ou novo, independentemente da data de compatibilidade utilizada. A flag deve ser adicionada explicitamente. A Cloudflare também manteve a implementação anterior em uso e afirma que os Workers publicados continuarão funcionando como antes.
A nova implementação também oferece suporte à importação em estágio de origem de módulos WebAssembly, retornando diretamente um objeto WebAssembly.Module, mas esse recurso funciona atualmente apenas com WebAssembly; qualquer outro tipo resulta em um erro de sintaxe. Portanto, a atualização não representa uma transição abrangente e sem restrições para todos os caminhos de carregamento de módulos, mas fornece uma base mais compatível que os desenvolvedores podem testar gradualmente.
Leitura editorial: o valor real da mudança está em levar o Workers de uma simulação parcial do comportamento dos módulos para um modelo mais próximo dos padrões de JavaScript e Node.js, o que pode reduzir as transformações impostas pelas ferramentas de compilação e tornar os aplicativos com vários módulos mais portáveis. No entanto, seu impacto final dependerá dos testes dos desenvolvedores com a nova flag, especialmente em dependências que usam require(), top-level await ou atributos de importação. Além disso, o fato de ela não ser ativada por padrão significa que a compatibilidade aprimorada ainda não se tornou o comportamento geral de todos os aplicativos.