TypeScript virou número 1 — e a IA tem parte da culpa

TL;DR

TypeScript ultrapassou JavaScript em novos projetos open-source grandes e isso não aconteceu por acaso: o sistema de tipos virou a infraestrutura de contexto compartilhado entre humanos e agentes de IA que programam em par.

  • Ultrapassagem concreta — State of JS 2024 e GitHub Octoverse 2024 colocam TypeScript à frente em novos repositórios, e frameworks como Next.js, NestJS, tRPC e Drizzle já assumem o tipo como default.
  • Tipos viraram contexto executável — uma assinatura `(id: UserId, t: Timestamp) => Promise>` carrega restrições de domínio que comentário nenhum codifica, e funciona como mapa de leitura para humanos e contrato verificado para agentes.
  • Agentes leem tipos melhor que comentários — Copilot, Cursor, Claude Code e Codex geram menos `any` baratos e importam contratos existentes quando o código é tipado; em bases JS puro, a mesma tarefa vira apropriação literal sem adaptação ao contexto.
  • O custo é honesto — sistema de tipos também atrapalha onde finge ajudar: generics inflados, `any` em borda, e a falsa sensação de segurança em runtime que o compilador não cobre.
  • JS puro ainda vence em casos pontuais — protótipos descartáveis, scripts de build one-off e glue code curto se beneficiam da ausência de cerimônia tipada, e TS não conserta isso.

~ 14 min de leitura · 2423 palavras

A ultrapassagem concreta: o que os números mostram

O cruzamento aconteceu entre 2023 e 2024, e foi registrado por mais de uma fonte independente. No State of JS 2024, TypeScript passou JavaScript em uso declarado entre respondentes, mantendo a liderança em satisfação pelo terceiro ano consecutivo. No GitHub Octoverse 2024, a participação de TypeScript entre novos repositórios públicos ultrapassou a de JavaScript em algumas categorias de tamanho. Não é unanimidade em todas as métricas, mas a direção é estável o suficiente para tratar como virada e não como flutuação.

Quatro sinais independentes confirmam a virada para o time de plataforma:

  • Frameworks assumem TypeScript como default — Next.js 15 tipa props de página por inferência; NestJS força decorators tipados; tRPC faz do contrato HTTP um tipo compartilhado entre cliente e servidor.
  • ORMs propagam tipos a partir do schema — Drizzle infere tipos a partir da declaração de tabelas e propaga até os repositórios; Prisma gera cliente tipado por geração de código; Kysely mantém o tipo da query alinhado com o schema do banco.
  • Ferramentas de build tratam tipo como entrada — Vite, esbuild e swc evoluíram para entender anotações de tipo e emitir código sem reescrita pesada.
  • Contratos entre serviços viraram tipos — tRPC, GraphQL codegen e OpenAPI geradores transformam especificação em tipos compartilhados que rodam em runtime e em build.

O dado relevante para o argumento deste post não é a métrica isolada, é o ecossistema que se formou em torno dela. O tipo deixou de ser camada adicional sobre o código: virou o código que organiza os módulos adjacentes. A leitura ingênua é “agora todo mundo usa TypeScript porque tipos são bons”. A leitura útil é outra: o tipo é um protocolo barato entre componentes escritos por pessoas e por modelos diferentes, e o ecossistema inteiro se reorganizou em torno desse protocolo. Ferramentas de build, linters, formatadores, ORMs e validadores passaram a tratar tipos como entrada, não como saída. Em 2026, começar projeto novo em TypeScript deixou de ser decisão; passou a ser default.

Tipos como contexto executável para humanos

A função estrutural do sistema de tipos é codificar restrições que não cabem em comentários nem em convenções de equipe. Um parâmetro `id: UserId` informa ao leitor que aquele valor não é qualquer string, é um identificador validado em alguma camada anterior. Um retorno `Promise>` informa que a função pode falhar com erro tipado, e que o chamador é obrigado a lidar com o caso de erro antes de seguir. Nada disso é decorativo; tudo isso é restrição que o compilador TypeScript verifica em cada call site.

O ganho para humanos se distribui em quatro eixos operacionais:

  • Substituição de leitura cruzada — em vez de abrir cinco arquivos para entender o que a função aceita, o leitor olha a assinatura TypeScript e segue.
  • Invariantes verificadas em compile-time — se a função declara `T extends NonNullable<...>`, o chamador não precisa confiar que o caller anterior já filtrou `null`, porque o tipo garante.
  • Refatoração estrutural — renomear um tipo propaga automaticamente para todos os call sites; em JavaScript puro, a mesma mudança vira varredura textual com revisão manual.
  • Documentação executável — `tsc –noEmit` no CI funciona como suite de testes barata que valida invariantes sem rodar o código.

O custo que pouca gente cita é o seguinte: o sistema de tipos é um modelo parcial da realidade. Ele cobre forma, não intenção. Uma função pode ter assinatura TypeScript perfeita e ainda fazer a coisa errada em runtime, e o compilador não tem como saber. A promessa de “segurança de tipos” é segurança contra uma classe específica de bug, não contra bug em geral. Esse ponto volta mais adiante, porque ele é central para decidir onde TypeScript ajuda e onde ele só atrapalha.

Em bases grandes, o tipo funciona como mapa de leitura cumulativo. Quanto mais código tipado existe, mais rápido um desenvolvedor novo consegue navegar, e mais barato fica refatorar contratos sem caçar usos manualmente. A busca “todos os call sites desta função” deixa de ser textual e passa a ser estrutural. Para times que mantêm software por anos, esse é o ganho que paga o investimento inicial em TypeScript.

A virada com IA: por que agentes leem tipos TypeScript melhor que comentários

Agentes de código — Copilot, Cursor, Claude Code, Codex, Continue, Cody — operam sobre uma janela de contexto limitada. Cada linha que não contribui para entender a intenção da próxima operação é custo. Comentários em prosa, com nuance, com piada interna ou com exemplo descolado do código adjacente, são ruído para o modelo. A assinatura de tipo TypeScript é o oposto: curta, estruturada, semanticamente densa, e mapeável em uma operação de parsing.

A diferença aparece em três classes de tarefa:

  • Geração de novo código a partir de descrição em linguagem natural — agentes em bases TypeScript importam contratos existentes em vez de reinventar: a função recebe `UserId` porque o resto do sistema passa `UserId`, e o modelo propaga isso sem precisar perguntar. Em bases JavaScript puro, o mesmo prompt gera `string` em todo lugar, e a inconsistência aparece na primeira revisão.
  • Geração de chamadas a APIs externas — tipos exportados pelos clientes HTTP viram restrições que o agente respeita; sem tipos TypeScript, o agente usa `any` por padrão e o caller descobre o problema em produção.
  • Preenchimento de formulários e formulários complexos — bibliotecas como `react-hook-form` com `zod` ou `@hookform/resolvers` permitem que o agente infira o tipo do payload a partir do schema de validação, eliminando divergência entre front e back.

Na navegação e leitura de código existente, tipos funcionam como índice. Um agente que precisa entender “quais funções recebem `OrderId`” consulta o grafo de tipos TypeScript e responde em segundos; o mesmo trabalho em código sem tipos vira grep textual com falsos positivos. Na refatoração assistida, renomear um tipo propaga automaticamente para todas as referências que o compilador TypeScript conhece; renomear uma string exige varredura textual e revisão manual de cada hit.

O ponto que merece honestidade: o ganho não é universal. Agentes ainda erram em inferência de tipos complexos, ainda inventam generics supérfluos, ainda confundem `T` com `U` em funções de ordem superior. A diferença é que o erro aparece na superfície do tipo, é detectado pelo compilador, e a iteração de correção é curta. Em bases JavaScript puro, o erro aparece em runtime, e a iteração de correção é produção. Para o ecossistema TypeScript, isso significa ciclos de PR mais curtos e depuração menos dependente de logs em produção.

O custo honesto: onde tipos atrapalham e onde eles fingem ajudar

Tipos atrapalham quando a abstração de tipos cresce além do que o domínio exige. Generics de quarto nível que mapeiam três camadas de inferência não tornam o código mais seguro — tornam o código mais caro de ler, mais caro de revisar, e mais caro de migrar quando o requisito muda. O sintoma típico é a equipe evitando mexer em um módulo porque “ninguém entende mais a assinatura TypeScript”. Esse é o momento em que o sistema de tipos deixou de cumprir a função que promete cumprir.

Três sinais de alerta que tipicamente acompanham o uso excessivo de tipos:

  • Hierarquia de generics com mais de três níveis de aninhamento — `T extends Record>>>` é sintoma de abstração inflada, não de modelagem correta.
  • Cast repetido no mesmo módulo — `as Foo` em três lugares do fluxo indica que o tipo declarado não corresponde ao tipo real, e o cast está mascarando a divergência.
  • Tempo de revisão dominado por discussão de tipos — PR que leva mais tempo discutindo a assinatura TypeScript do que o comportamento que a função executa é PR que precisa de redesign, não de mais discussão de tipos.

Tipos fingem ajudar quando o programador usa o tipo como desculpa para não testar. `as User` em três lugares do fluxo, com casts que silenciam o compilador TypeScript, movem o bug de categoria mas não o eliminam. O compilador passa, o typecheck passa, o CI passa, e o usuário recebe `undefined.user_name` em produção. O sistema de tipos não cobre runtime, e qualquer cast que afrouxa a verificação transfere a responsabilidade de volta para o programador.

Tipos atrapalham em camadas de borda onde o domínio é mal definido. Parsers de entrada externa, integrações com APIs de terceiros sem contrato tipado, scripts de migração de dados com schema em mutação, todos esses casos vivem na fronteira entre “tipo conhecido” e “tipo a descobrir”. TypeScript oferece ferramentas (`unknown`, `zod`, validação em runtime), mas usar essas ferramentas certo custa mais do que usar JS puro no mesmo arquivo. A regra prática: se o módulo existe para traduzir entre dois mundos, considere JS puro com testes unitários pesados.

A métrica para distinguir “tipo que ajuda” de “tipo que atrapalha” é simples e raramente aplicada. Quantas linhas de teste o módulo exigiu após receber os tipos? Quantos PRs posteriores tocaram a assinatura por motivo não funcional? Quantas vezes a equipe regrediu o tipo em vez de consertar o código subjacente? Se as três respostas cresceram sem retorno claro de qualidade, o tipo virou overhead. A regra que separa os dois cenários não é “ter mais tipos”: é “ter tipos que sustentam refatoração”.

Não é conserto para tudo: casos onde JavaScript puro ainda vence

Casos de uso onde JavaScript puro vence são pontuais, mas existem. A regra que organiza os quatro cenários a seguir é a mesma: o ganho de TypeScript depende de quem vai ler e refatorar o código nos próximos meses; quando não há quem, a tipagem é overhead.

Quatro cenários onde JavaScript puro é a escolha defensável:

  • Protótipos descartáveis — prazo de vida medido em horas; nenhum arquivo `tsconfig.json` para manter, nenhuma decisão sobre `strict` ou `noImplicitAny`, nenhum ciclo de typecheck no script.
  • Scripts únicos de migração ou ETL — código de 80 linhas que migra dados uma vez e é apagado; TypeScript nesse contexto é overhead puro.
  • Glue code curto entre dois sistemas já tipados — middleware de 30 linhas que pega `Request`, faz parse, e chama um handler tipado não ganha nada com tipos próprios.
  • Módulos exploratórios em fase de discovery — o domínio ainda está sendo mapeado, e tipos cristalizam decisões prematuras que serão desfeitas nos próximos sprints.

A tipagem do DOM é verbosa, frequentemente desatualizada em relação à versão instalada, e o ganho de segurança raramente paga o custo de leitura. Para manipulação direta de DOM em protótipos rápidos, especialmente quando o destino é um único framework e a equipe já domina as primitivas, JavaScript puro continua sendo a escolha pragmática.

Por fim, módulos exploratórios onde o domínio ainda está sendo mapeado se beneficiam de JS puro. Tipos cristalizam decisões prematuras. Em fase de discovery, deixar o código como JS puro e anotar com comentários permite iterar sem reescrever assinaturas. Quando o domínio estabiliza, migrar para TypeScript é trabalho mecânico, e o ganho posterior compensa. A regra prática: tipar quando o modelo mental do domínio está maduro; deixar solto quando ainda está em movimento.

Como decidir TypeScript (e como decidir errado) a partir de agora

A decisão de adotar TypeScript em um projeto novo deixou de ser ideológica e passou a ser econômica. O critério é o horizonte de manutenção do código e a probabilidade de refatoração nos próximos 18 meses. Projeto novo com horizonte de manutenção acima de um ano e time acima de três pessoas: tipar por default. Projeto descartável, protótipo, ou script único: JavaScript puro é defensável.

Cinco critérios para decidir onde tipar e onde deixar solto:

  • Horizonte de manutenção — acima de 18 meses, TypeScript compensa; abaixo, é overhead.
  • Tamanho do time — três ou mais pessoas compartilhando o código, o sistema de tipos funciona como índice compartilhado; sozinho, o ganho é marginal.
  • Velocidade de mudança do domínio — domínio estável favorece tipagem forte; domínio em mutação semanal favorece genericidade controlada e revisão periódica das assinaturas.
  • Existência de integrações externas com contrato — APIs tipadas no cliente e no servidor pagam o custo de propagação de tipos; integrações sem contrato pagam o custo de validação em runtime com `zod` ou similar.
  • Uso de agentes de IA — se o time usa Copilot, Cursor ou Claude Code no fluxo diário, a tipagem é pré-requisito para qualidade do output gerado; sem tipagem, o agente erra mais e itera mais.

A decisão de migrar uma base JavaScript existente para TypeScript é mais cara e exige critério diferente. Migrar tudo de uma vez raramente compensa; migrar por camadas, começando pelos módulos mais reutilizados e mais críticos, tem retorno mais previsível. A regra empírica é começar pelos módulos que mais recebem PRs e pelos contratos que mais mudaram no último ano — esses são os módulos onde o ganho de TypeScript se paga primeiro.

O erro mais comum é confundir “ter TypeScript configurado” com “ter o ganho de TypeScript”. Uma base com `tsconfig.json` mínimo, `strict: false`, e `any` espalhado nas bordas tem o pior dos dois mundos: o custo da tipagem sem o benefício da verificação. Se a decisão é adotar TS, a decisão coerente é adotar TypeScript estrito e migrar os `any` existentes em ondas. Configuração frouxa com tipos espalhados é a configuração que mais produz PRs de “fix typo no tipo” sem entregar valor real.

O segundo erro comum é usar o sistema de tipos como mecanismo de documentação e esquecer a documentação em si. Tipos dizem qual é a forma esperada; comentários dizem qual é a intenção. Em módulos complexos, ambos são necessários, e nenhum substitui o outro. O custo de manter os dois é real, e a equipe precisa estar disposta a pagar antes de configurar `// @ts-check` em arquivos `.js` ou ativar `strict` em bases existentes.

A conclusão operacional: medir antes de mudar, medir durante, medir depois. Tipo sem métrica é opinião; métrica sem tipo é anotação manual. O retorno aparece quando os dois andam juntos. Em 2026, a pergunta deixou de ser “devemos adotar TypeScript” e passou a ser “em quais partes da nossa base o tipo ainda não compensa o investimento”. Para a maioria das bases com mais de um ano e mais de três pessoas mantendo, a resposta é: nenhuma.

Leitura relacionada

Posts Similares

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *