
WebKit reescreve carregador de módulos para consertar top-level await
O que muda
O WebKit diz que o Safari 27 passa a ter conformidade total com a especificação para top-level await. Antes, desenvolvedores podiam encontrar erros inesperados de "accessed before initialization". A equipe afirma ter corrigido o problema na raiz, reescrevendo o carregador de módulos do Safari do zero. Segundo o texto, os ganhos vão além do recurso: com o carregador reconstruído sobre a base correta, os módulos ES passam a ser algo em que se pode confiar no Safari. Para testar, há o Safari Technology Preview 251 e a beta do Safari 27.
O que é top-level await
Top-level await permite usar await no nível superior de um módulo, dando aos autores a mesma conveniência que funções assíncronas oferecem para simplificar cadeias de Promises. Em módulos ES, quando um módulo encontra um top-level await, qualquer módulo que o importe também fica suspenso até que o await resolva; módulos irmãos no grafo de dependências que não dependem do módulo em espera podem continuar executando em paralelo.
A origem do problema
O carregador do Safari foi escrito há muito tempo, na época da proposta WHATWG Loader (atualizada pela última vez em janeiro de 2016), e dependia da especificação de funcionalidade definida pelo host contida nela. Isso funcionava quando a execução de módulos era puramente síncrona e o top-level await não existia. O ECMAScript 2022 introduziu o top-level await, mas a proposta WHATWG Loader já havia caído em desuso após ser substituída pela seção de módulos do padrão ECMAScript - e o carregador do Safari continuava baseado nela. O recurso foi implementado segundo uma proposta abandonada antes de existir suporte a async/await no ECMAScript, e não conforme os algoritmos de execução assíncrona de módulos da especificação. Esse descompasso causou bugs sutis que a equipe não conseguiu resolver por completo durante anos, apesar de várias tentativas.
O bug em ação
Um exemplo do artigo carrega dinamicamente o mesmo módulo três vezes. A ordem esperada era 1, 2, 3, mas o carregador antigo produzia 2, 3, 1, além de erros de acesso a someArray antes da inicialização. A causa: ao começar a executar o módulo com top-level await, o carregador pausa no await e devolve o controle ao módulo principal, que inicia a segunda importação; a promessa da segunda importação não deveria resolver antes do fim da avaliação da primeira, mas resolvia imediatamente. Com o novo carregador, a saída é a esperada.
Builtin em JavaScript ou C++ nativo
O carregador antigo era escrito em JavaScript como builtin auto-hospedado. Builtins em JavaScript podem ser embutidos no código que os invoca, evitando o custo de cruzar a fronteira entre JavaScript e C++, e a criação de objetos é mais rápida. Mas a equipe aponta desvantagens: inicialização mais lenta, por exigir compilação em tempo de execução; características de uso amplas, que dificultam aos compiladores JIT do JavaScriptCore explorar padrões; e o carregador não ser um caminho quente, o que reduz ainda mais o benefício. O desempenho geral era menos estável e previsível que o de C++. Na reescrita, a escolha foi código totalmente nativo.
A reescrita e a validação
A reescrita começou em janeiro de 2026 e apagou todo o arquivo JavaScript do carregador antigo. A equipe traduziu o pseudocódigo da especificação ECMAScript para C++, função por função, montando um grafo de fluxo para decidir a ordem. Funções folha como ExecuteModule e ModuleRequestsEqual vieram primeiro; em poucas semanas os casos mais comuns funcionavam e um pull request preliminar foi aberto. Engenheiros da Bun, cujo runtime é construído sobre o JavaScriptCore e herdou os problemas antigos, cederam casos de teste. A equipe escreveu um fuzzer que gera grafos complexos de módulos, com e sem top-level await, e comparou a saída do JavaScriptCore com a de outras engines, byte a byte. Com os testes de módulos do test262 passando e muitos testes do WPT antes falhos corrigidos sem regressões, o novo carregador ficou pronto para merge, depois de semanas de uso diário de uma build.
Conclusão
Com o lançamento do Safari 27, será possível publicar aplicações web que usem top-level await em produção. Até lá, a recomendação é baixar a beta e testar, enviando relatos de problemas em bugs.webkit.org.