Hedge no seu runtime JavaScript: o playbook de um CTO após o recuo do Bun

Por Diogo Hudson Dias
A CTO and senior engineer review a runtime-agnostic architecture diagram on a whiteboard in a São Paulo office.

O suporte ao Bun está sendo limitado e descontinuado em alguns lugares. O Deno acabou de lançar outra grande versão. Provedores de Edge seguem apertando e afrouxando limites de recursos. Se o seu backend presume que um único runtime JavaScript ficará estável por anos, você está fazendo lock-in de fornecedor do jeito mais difícil. Você precisa de um hedge de runtime — não porque você quer trocar, mas porque você quer o direito de trocar sem um trimestre inteiro de retrabalho.

Por que isso importa agora

Três manchetes nas últimas semanas deveriam recalibrar seu modelo de risco:

  • A volatilidade de runtimes é real. O Deno 2.8 saiu; ótima velocidade e também um lembrete de que ecossistemas não-Node continuam avançando rápido e mudando padrões.
  • O suporte pode desaparecer. O suporte ao Bun agora é limitado ou descontinuado em várias ferramentas, o que significa que seu CI ou host pode simplesmente deixar de prometer compatibilidade.
  • Fornecedores mudam de rumo. A Microsoft cancelando certas licenças de ferramentas de IA é outro domínio, mas a mesma lição: quando você aluga capacidades, seu roadmap herda risco de políticas externas.

Você pode manter a velocidade do JavaScript moderno enquanto isola o core do negócio da volatilidade de runtimes. O truque é adotar uma estratégia de subconjunto portátil + adaptador, medir o imposto (ele existe) e comprar uma opção: a capacidade de fazer um pivô em semanas, não em trimestres.

A planilha de risco de runtimes para o CTO

Coloque números ao lado destes modos de falha antes de escolher um runtime (ou dobrar a aposta em um):

  • Deriva de API: ESM vs. CommonJS, Web APIs padrão vs. módulos exclusivos do Node, diferenças sutis em streams, crypto e timers.
  • Limites de execução: isolates de edge com limites de memória de 128–256 MB vs. contêineres Node com 512 MB–1 GB; cotas de tempo de CPU e relógios de parede por requisição variam por provedor.
  • Addons nativos: Qualquer coisa que exija Node-API ou variantes específicas de libc (glibc vs. musl) é uma âncora. Migrar entre AWS Lambda, contêineres e isolates vira um exercício de rebuild.
  • Lock-in de toolchain: Um test runner, bundler ou gerenciador de pacotes específico de um runtime pode transformar uma migração “pequena” em uma reescrita completa do pipeline.
  • Armadilhas de licenciamento: Trazer deps transitivas AGPL para componentes de servidor pode compelir divulgação de código — uma superfície separada, porém adjacente, que você precisa observar ao experimentar novos runtimes e forks.

Atribua pontuações de probabilidade e raio de impacto para cada um nos seus principais workloads. Se o raio de impacto é “API de pagamentos fora” ou “não dá para publicar um fix de segurança”, faça hedge.

O framework de decisão: escolha pelo workload, não pelo hype

Um runtime não servirá para tudo. Mapeie seus tipos de workload para o modelo de execução certo e, então, aplique disciplina de portabilidade onde importa.

1) Requisição/resposta sem estado e crítica em latência

  • Exemplos típicos: renderização no edge, roteamento A/B, personalização, emissão de tokens de auth, adaptadores simples.
  • Alvo: isolates de edge ou serverless leve que implementem o conjunto de Web APIs do WinterCG (fetch, Request/Response, Web Crypto, URL, Web Streams).
  • Disciplina: Apenas ESM, sem built-ins do Node (http, net, tls), evitar fs, sem addons nativos; mantenha o código puro e com poucas dependências.
  • Por quê: Cold starts costumam ser de milissegundos de um dígito em isolates; menor latência para o usuário; maior eficiência de escala. Você sacrificará sockets e I/O de arquivos — aceite isso.

2) Processos com estado ou em lote com I/O

  • Exemplos típicos: ETL, processamento de mídia, geração de PDF, workers de fila, webhooks longos, intermediação de modelos de IA.
  • Alvo: Node.js LTS em contêineres ou serverless com compute generoso (e idealmente sidecars de GPU quando necessário).
  • Disciplina: Mantenha todas as partes não portáteis (fs, addons nativos, headless Chrome) atrás de uma interface estreita para que possam virar um serviço depois.
  • Por quê: Você precisa de semântica estável de arquivos/rede, memória previsível e debugging maduro. Aqui, vazão vence latência de cauda.

3) Ferramentas de desenvolvedor e CLIs

  • Exemplos típicos: geradores de projeto, scaffolding interno, scripts de migração.
  • Alvo: Deno ou Bun podem ser excelentes, especialmente pela facilidade de distribuição e velocidade.
  • Disciplina: Não deixe escolhas de runtime de ferramentas vazarem para suas libs de produção. Mantenha as libs de produção agnósticas de runtime.
  • Por quê: Você pode trocar o runtime de uma CLI com baixo impacto no cliente; é um lugar seguro para explorar ergonomias de ponta.

O subconjunto portátil: construa sobre padrões da Web primeiro

A maior parte do hedge vem de se apoiar nas APIs em que todo runtime moderno está convergindo. Priorize estes:

  • fetch / Request / Response: Primitivas universais de cliente/servidor HTTP. No Node 18+, fetch é embutido; em outros lugares é de primeira classe. Evite os módulos http/https do Node.
  • Web Crypto (SubtleCrypto): Use digest, sign, verify e geração de chaves padrão; evite polyfills de crypto atrelados a semânticas exclusivas do Node.
  • URL e URLSearchParams: Não use parsers customizados.
  • TextEncoder / TextDecoder e Web Streams: Prefira transform streams em vez dos tipos de stream exclusivos do Node; a interoperabilidade está melhorando entre runtimes.
  • Apenas ESM: Nada de CommonJS. Exports condicionais quando necessário, mas não introduza require dinâmico.

Sempre que você sentir vontade de importar um builtin do Node, pare e pergunte: Existe uma forma compatível com o WinterCG? Se não, isso pode viver atrás de uma fronteira?

Antipadrões que matam a portabilidade

  • Apoiar-se em fs para armazenamento temporário em código destinado a edge/serverless. Use object storage via fetch; ou um cache apenas em memória com tamanho limitado.
  • Addons nativos para compressão, operações de imagem ou crypto quando existem alternativas WASM ou em JS puro de alta qualidade. Se precisar usar nativo, isole-o.
  • Node Streams em todo lugar. Migre para Web Streams para pipelines de request/response.
  • Fazer polyfill global de Buffer ou outros globais do Node. Prefira imports explícitos e utilitários portáteis.
  • SSR que assume APIs do Node para leitura de arquivos, resolução de paths ou sockets de rede. Mantenha a renderização pura; alimente-a com dados via fetch.

Sua camada de adaptadores: a menor caixa com o maior retorno

Crie um único pacote interno que expresse suas necessidades estreitas específicas de runtime. Superfície típica:

  • kv.get/set/delete: apoiado por Redis no Node, por um KV do provedor no edge ou por um store durável de objetos.
  • secrets.get(name): mapeia para process.env no Node, bindings de ambiente no edge e store local criptografado no dev.
  • scheduler.delay(fn, ms): usa setTimeout no Node, alarms do provedor em ambientes de edge e um fallback no-op em testes locais.
  • storage.put/get: cliente fetch compatível com S3; nunca fs bruto para caminhos portáteis.

Depois escreva de duas a quatro implementações concretas: Node, Edge e qualquer host de caso especial de que você dependa. Seu código de aplicação importa a interface, não o host.

Testes de conformidade: torne a deriva visível

Portabilidade sem uma matriz de testes é teatro. Adicione um job que rode seus testes unitários e de integração nestes alvos:

  • Node.js: LTS atual e anterior (para 2026, pense em 20.x e 22.x).
  • Deno: estável atual (2.x). Use o modo de compatibilidade com Node apenas em testes do adaptador, não no código da aplicação.
  • Bun: fixe uma versão comprovadamente boa para tooling de dev e adaptadores; falhe rápido se as semânticas diferirem.
  • Emulador de edge: emulador local de isolate (por exemplo, estilo Miniflare) para validar suposições de Web APIs.

Bloqueie merges nessa matriz para as bibliotecas que precisam permanecer portáteis. Espere um imposto de 10–15% no tempo de build e refactors ocasionais. É mais barato do que uma migração forçada sob pressão de outage.

Checagem de realidade de performance

Os números variam por provedor e app, mas o formato do trade-off é confiável:

  • Cold start: edge baseado em isolates costuma acordar em milissegundos de um dígito; funções Node genéricas em serverless tipicamente levam dezenas a centenas de milissegundos dependendo do runtime e do tamanho de memória.
  • Memória: isolates de edge comumente limitam ~128–256 MB por requisição; contêineres/funções Node oferecem 512 MB ou mais.
  • Tempo de CPU: edge impõe tetos apertados de compute por requisição; contêineres dão CPU estável e são mais amigáveis a transforms de streaming e tarefas CPU-bound.

Não escolha um runtime para “vencer benchmarks”. Escolha para cumprir um SLO claro: latência P95, vazão e custo por 1K requisições. Depois use seu adaptador para colocar cada função na camada de execução certa.

Segurança e compliance não param por causa de runtimes

Fazer hedge de runtime também é uma jogada de segurança:

  • SBOMs prontos para SSA/CSA: gere e armazene SBOMs por build e por runtime. Se um CVE surgir em uma transitiva exclusiva do Node, você consegue cortar para uma variante segura para edge mais rápido.
  • Varredura de licenças: adicione uma etapa que sinalize AGPL, SSPL ou licenças não validadas em código executado no servidor. Os casos públicos recentes em torno de violações de AGPL são alertas, não rodapés.
  • Disciplina de segredos: nada de gambiarras apenas com dotenv; seu adaptador deve centralizar o acesso a segredos e tornar explícito o que é local vs. produção.

Pessoas e processo: mantenha a velocidade enquanto faz hedge

Brazil tem 750K+ desenvolvedores e uma base ampla de talentos em Node/TypeScript. A maioria ainda não colocou Deno ou Bun em produção — e tudo bem. As habilidades de portabilidade de que você precisa são ensináveis em semanas:

  • Semana 1–2: Treine em Web APIs (fetch, Web Crypto, Web Streams) e práticas apenas ESM; elimine CommonJS em código novo.
  • Semana 3–4: Introduza o padrão de adaptador; migre um serviço não crítico para usá-lo.
  • Contínuo: Mantenha um perfil de lint “agnóstico de runtime” e faça cumprir em PRs para pacotes portáteis.

O custo: espere uma queda de 5–10% na velocidade bruta de features para times que possuem módulos portáteis. O benefício: quando você precisar de fato trocar de runtime, normalmente economiza 4–6 semanas de trabalho de migração pontual e evita um congelamento arriscado.

Seu plano de hedge de runtime em 90 dias

Dias 0–30: inventário e limites

  • Faça inventário de todas as chamadas específicas de runtime (built-ins do Node, addons nativos, polyfills globais). Classifique por criticidade e capacidade de substituição.
  • Defina seu subconjunto portátil e codifique-o em regras de lint. Apenas ESM para todo código novo.
  • Coloque de pé um pequeno pacote de adaptador e implemente versões Node e Edge para kv, secrets, scheduler, storage.
  • Adicione um job de teste de conformidade ao CI entre Node LTS e um runtime alternativo. Faça falhar módulos greenfield que desviem.

Dias 31–60: primeiro port e observabilidade

  • Migre um endpoint sensível a latência para o subconjunto portátil e publique em um runtime de edge lado a lado com seu caminho em Node. Roteie 1–5% do tráfego e meça.
  • Migre um worker em lote para isolar deps nativas atrás do adaptador. Se for impossível, registre isso como dívida técnica deliberada com um plano de saída.
  • Instrumente tags de runtime/versão/região em cada linha de log e trace. Construa dashboards de SLO por runtime.

Dias 61–90: expandir e impor

  • Portar 30–50% dos seus endpoints de requisição/resposta que caibam nas restrições de edge; deixe o resto em Node.
  • Endureça o adaptador (testes de caos entre implementações, timeouts, backoffs). Documente-o como se fosse uma API pública.
  • Torne a matriz de conformidade um gate de merge para qualquer biblioteca destinada a ser reutilizada entre serviços.

Trade-offs e quando dizer não

Hedging não é de graça. Diga não à portabilidade quando:

  • Você realmente precisa de recursos exclusivos do Node ou nativos e não há alternativa realista (por exemplo, renderização de PDF de alta fidelidade com headless Chrome). Mantenha isso em um serviço delimitado.
  • O código é descartável (um script de migração pontual). Prefira velocidade e apague-o após o uso.
  • Seu time está sob pressão aguda de entrega. Coloque a portabilidade no timebox do adaptador e do lint; agende o resto para o próximo ciclo.

Caso contrário, compre a opção. Hoje é o suporte ao Bun ficando instável; amanhã pode ser uma mudança de faturamento ou uma capacidade de plataforma de que você de repente precisa e que seu runtime atual não entrega.

Como é o “bom” em seis meses

  • Você consegue rodar bibliotecas críticas em Node LTS e em pelo menos um runtime alternativo sem mudanças de código além da ligação do adaptador.
  • Seus SLOs são por runtime, e você tem gráficos provando onde o edge vence e onde o Node vence.
  • Você tem zero código de cola de runtime indiferenciado fora do adaptador; todo o resto é lógica de aplicação.
  • Desenvolvedores priorizam Web APIs primeiro. Code review intercepta imports exclusivos do Node instantaneamente.

Essa postura lhe dá poder de barganha com fornecedores, menos surpresas às 2 da manhã e um funil de contratação que aproveita o enorme mercado de TypeScript (incluindo 6–8 horas de sobreposição US/Brazil) sem treinar engenheiros em um beco sem saída.

Principais conclusões

  • Não aposte seu backend na boa vontade de um único runtime JavaScript. Compre a opção de trocar com um subconjunto portátil e uma camada de adaptador.
  • Escolha runtimes por workload: edge para caminhos sem estado e puros em latência, Node LTS para jobs com I/O pesado e de longa duração, runtimes de ponta para ferramentas/CLIs.
  • Apoie-se nos padrões da Web (fetch, Web Crypto, Web Streams, URL) e em ESM-only; evite módulos exclusivos do Node em código portátil.
  • Rode uma matriz de conformidade entre Node LTS e pelo menos um runtime alternativo; use-a como gate de merge para bibliotecas compartilhadas.
  • Espere um imposto de 5–10% na velocidade por portabilidade; ele paga 4–6 semanas de tempo de migração quando um pivô é necessário.
  • Centralize segredos, storage, kv e agendamento em um pequeno adaptador; trate-o como um produto com testes e documentação.

Autor: Diogo Hudson Dias

Ready to scale your engineering team?

Tell us about your project and we'll get back to you within 24 hours.

Start a conversation