Como migrar um projeto de JavaScript para TypeScript com o Claude Code

Migrar JavaScript para TypeScript não precisa ser um big bang: dá pra fazer em fatias, com o projeto rodando o tempo todo. O caminho é gerar o tsconfig.json com tsc --init, ligar allowJs pro projeto aceitar .js e .ts juntos, ligar checkJs pra ver os erros antes de converter, renomear um arquivo por vez e rodar tsc --noEmit entre os lotes. O Claude Code entra como executor (Plan mode pra mapear a ordem, subagente por lote) e o compilador entra como juiz. Depois vem noImplicitAny, strict e a faxina das supressões
O medo de migrar um projeto pra TypeScript não é o TypeScript
É quebrar o projeto no meio do caminho e ficar naquele limbo onde nada compila e nada roda 😅
A boa notícia: essa migração não precisa ser um big bang
Dá pra fazer em fatias, com o projeto de pé entre uma etapa e outra, usando o Claude Code como executor e o compilador como juiz
Aqui vai um passo a passo de migração incremental, com o que pedir ao agente, o que rodar pra conferir e onde a conversão automática costuma esconder erro
Sem promessa de conversão mágica, beleza? Isso não existe
Se você ainda tá pegando o ritmo do agente, vale antes dar uma olhada no fluxo completo do Claude Code, que é onde esse tipo de tarefa longa se encaixa
O que ter pronto antes de pedir a primeira conversão
Antes de soltar o agente em cima do código, monta o campo de jogo
São quatro coisas, nenhuma delas demora:
- Um
tsconfig.json, gerado comtsc --init, que já vem com um conjunto das opções mais comuns listadas e comentadas - Um comando de checagem usando
noEmit, que faz o compilador checar os tipos sem gerar nenhum arquivo de saída (nem JS, nem source map, nem declaração) - Controle de versão limpo, sem nada pendente, pra você conseguir voltar atrás quando um lote sair torto
- Um
CLAUDE.mdcom as regras da migração, porque é o arquivo de instruções persistentes que o Claude lê no início de cada sessão
Domine o Claude Code do básico ao avançado
Você vai aprender a criar sistemas completos com Claude Code, sem precisar ser programador. Inscreva-se para ter acesso a um desconto de lançamento e bônus especiais!
Sobre esse último: o comando /init gera um CLAUDE.md inicial e o /memory serve pra refinar
É ali que você escreve as regras que não podem sumir quando o contexto rodar: um lote por vez, não mexer em arquivo fora do lote, rodar a checagem antes de dizer que terminou, por aí vai
Se a mesma instrução se repete em todo projeto seu, dá até pra empacotar isso e criar skills no Claude Code em vez de recolar texto
E tem um detalhe de operação que muda tudo: o Shift+Tab cicla entre os modos de permissão (Manual, Accept edits, Plan e Auto)
No modo Plan, o Claude explora o projeto e propõe um plano sem editar os arquivos de código
É exatamente o que você quer no começo de uma migração: alguém olhando o mapa, não alguém já renomeando arquivo 🙂
Passo a passo da migração com o Claude Code
A ordem abaixo tem uma lógica: primeiro você deixa JS e TS conviverem, depois liga a checagem, e só no fim aperta o parafuso
Apertar o parafuso antes é o que gera aquela tela de 400 erros que faz todo mundo desistir
1. Plan mode: mapear a ordem dos arquivos antes de tocar em qualquer um:
Shift+Tab até cair no Plan e pede o mapa
Algo assim:
Mapeie os módulos deste projeto por dependência.
Quero uma ordem de migração JS -> TS começando pelas folhas
(arquivos que ninguém importa de volta) e terminando nos entrypoints.
Não edite nada, só me devolva a lista em lotes de 3 a 5 arquivos.
O agente lê e propõe, sem editar código
O erro comum deste passo: aceitar o plano sem olhar
A ordem folhas primeiro é uma convenção prática, não uma regra do compilador, então trate como sugestão e confira se faz sentido no SEU projeto
2. Gerar o tsconfig e ligar o allowJs:
npx tsc --init
Depois liga o allowJs, que é a opção que permite arquivos JavaScript serem importados dentro do projeto, e não apenas .ts e .tsx
{
"compilerOptions": {
"allowJs": true,
"noEmit": true
}
}
Esse é o segredo da migração em fatias: com allowJs ligado, .js e .ts moram juntos e o projeto continua rodando enquanto você converte
O erro comum deste passo: esquecer o allowJs e ver o build reclamar de import de arquivo que sempre existiu
3. Ligar o checkJs pra ver os erros ANTES de converter:
Essa é a parte que quase todo mundo pula
O checkJs funciona em conjunto com o allowJs e faz o compilador reportar erros nos arquivos JavaScript
Ele equivale a colocar // @ts-check no topo de todos os .js incluídos no projeto
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true
}
}
Se ligar em tudo de uma vez virar um mar vermelho, dá pra ir de arquivo em arquivo: basta o comentário // @ts-check na primeira linha do .js
E onde o arquivo ainda vai ficar .js por um tempo, JSDoc resolve, porque comentário JSDoc pode ser usado pra adicionar informação de tipo a código JavaScript, sem converter o arquivo:
// @ts-check
/**
* @param {string} nome
* @param {number} total
* @returns {string}
*/
function resumo(nome, total) {
return `${nome}: ${total}`
}
Muito massa isso, porque você já colhe tipo antes de renomear um arquivo sequer
O erro comum deste passo: pedir pro agente "consertar todos os erros do checkJs" de uma vez
Aí ele sai editando meio mundo de arquivo e você perde a régua de review
4. Renomear o PRIMEIRO arquivo .js para .ts:
O primeiro passo da migração descrito no manual oficial do TypeScript é exatamente esse: renomear um dos seus arquivos .js para .ts
Um arquivo
Depois roda o juiz:
npx tsc --noEmit
Como o noEmit não gera arquivo de saída, essa rodada é só checagem de tipo, rápida e sem sujeira no disco
O erro comum deste passo: deixar o agente renomear 20 arquivos porque "tava indo bem"
Quando quebra, você não sabe qual dos 20 quebrou
5. Repetir por lotes pequenos, com o projeto rodando entre eles:
Aqui é o loop da migração inteira:
- renomeia o lote (3 a 5 arquivos)
- roda
npx tsc --noEmit - roda o projeto/os testes
- commita
- próximo lote
Rodar o projeto entre os lotes não é frescura: type-check verde e app quebrado são coisas diferentes, principalmente quando o rename mexeu em extensão de import
Dá pra automatizar parte disso com hook
Hooks do Claude Code são configurados no settings.json (por exemplo ~/.claude/settings.json) dentro de um objeto hooks, e um hook PostToolUse com matcher Edit|Write dispara um comando depois de cada edição de arquivo:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npx tsc --noEmit" }
]
}
]
}
}
Tome cuidado com a expectativa aqui: o PostToolUse não consegue bloquear a operação, porque a ferramenta já rodou
O que ele faz é devolver a saída de stderr pro Claude ver, ou seja, o agente fica sabendo que quebrou e pode corrigir
É feedback, não cadeado
6. Apertar o noImplicitAny:
Com a base já em .ts, entra a caça aos any invisíveis
O noImplicitAny faz o TypeScript emitir erro sempre que ele teria inferido o tipo any silenciosamente
{
"compilerOptions": {
"noImplicitAny": true
}
}
O erro comum deste passo: pedir pro agente "resolver os erros" e ele anotar tudo como any explícito
Parabéns, o erro sumiu e a tipagem também haha
Revisa parâmetro por parâmetro
7. Ligar o strict:
A flag strict é um guarda-chuva: ela liga várias opções de checagem estrita de uma vez, a família strict, com coisas como strictNullChecks e strictFunctionTypes
{
"compilerOptions": {
"strict": true
}
}
Por isso ela vem no fim, e não no início
Ligar strict no dia 1 de uma base JS grande é o caminho mais curto pra abandonar a migração
8. Higiene das supressões:
Toda migração acumula supressão pelo caminho, e tudo bem
O problema é a supressão que fica lá pra sempre
Troca // @ts-ignore por // @ts-expect-error
Porque o // @ts-ignore não faz nada se a linha seguinte não tiver erro (ele fica calado), enquanto o // @ts-expect-error reporta Unused @ts-expect-error directive quando a supressão deixa de ser necessária
Traduzindo: um deles te avisa quando a dívida foi paga, o outro te deixa carregando ela pra sempre
Onde a conversão automática esconde erro (e o que revisar à mão)
A conversão automática acerta MUITO na parte mecânica: renomear, ajustar import, criar assinatura óbvia, mover module.exports pra export
O que ela não faz é entender a sua regra de negócio
E é aí que mora o problema, porque o resultado compila e você acha que acabou
Sintoma: o arquivo virou .ts, o tsc --noEmit passou verde, mas nada tá realmente tipado
Causa: o noImplicitAny ainda tá desligado, então tudo que o compilador não conseguiu inferir virou any no silêncio
Solução: ligar noImplicitAny e revisar um a um, no lugar de aceitar o verde
Sintoma: um erro que existia ontem sumiu e ninguém consertou nada
Causa: tem um // @ts-ignore novo na linha de cima
Solução: trocar por // @ts-expect-error, que pelo menos reclama quando a supressão virou desnecessária, e revisar no diff toda supressão que apareceu no lote
Sintoma: o tipo passa no compilador mas não descreve o dado real
Causa: inferência a partir de um uso feliz, do tipo { id: number, nome: string } porque foi assim que o objeto apareceu naquele arquivo, ignorando que a API às vezes devolve nome vazio ou nulo
Solução: revisar à mão a assinatura pública (o que sai do módulo e é usado por outros) e os valores que podem ser nulos, com o strictNullChecks da família strict ajudando a puxar a orelha
Como prevenir tudo isso? Lote pequeno, tsc --noEmit entre as etapas e disciplina de contexto
Se a sessão ficar longa (e ela FICA, migração é conversa comprida), lembra que o /clear zera o contexto da conversa (a sessão anterior continua em disco e pode ser retomada pelo ID) e o /compact resume o histórico pra liberar contexto, aceitando instrução de foco
Sessão entupida é onde o agente começa a esquecer a regra do lote…
Migração guiada pelo agente ou ferramenta automática: quando cada uma faz sentido
Não é briga, são cenários diferentes
Projeto pequeno, ou com regra de negócio delicada, pede conversão em lotes conduzida no Claude Code
E tem um recurso que cai bem aqui: cada subagente roda na própria janela de contexto, com system prompt próprio e acesso de ferramentas próprio
As chamadas intermediárias ficam dentro do subagente e só a mensagem final volta pra conversa principal, e vários podem rodar em paralelo
Ou seja: um subagente por lote, a conversa principal recebendo só o resultado, e o seu contexto principal não vira um depósito de log
Base grande e repetitiva é outra conversa
Aí faz sentido olhar pro ts-migrate, ferramenta open source mantida no repositório airbnb/ts-migrate, que pega um projeto JavaScript ou parcialmente TypeScript e devolve um projeto TypeScript que compila
Repara na frase: que compila
| Situação | Caminho que combina | O que você ganha | O que continua com você |
|---|---|---|---|
| Projeto pequeno ou regra de negócio delicada | Lotes conduzidos no Claude Code, subagente por lote | Revisão a cada etapa, projeto rodando entre os lotes | Ler o diff e revisar os tipos gerados |
| Base grande e repetitiva | ts-migrate pra passada mecânica |
Projeto que compila, ponto de partida uniforme | Rever os tipos depois, um módulo por vez |
E fica com isso na cabeça: compilar não é o mesmo que estar tipado
Uma passada automática te dá o esqueleto
O tipo que descreve o dado de verdade é revisão humana, com ou sem agente do lado
Próximo passo
A régua da migração cabe em uma linha: um lote por vez, projeto rodando entre as etapas, compilador como juiz
O agente é o executor rápido, não o revisor final
O próximo passo concreto é ridículo de simples: pega UM arquivo .js hoje, renomeia pra .ts e roda
npx tsc --noEmit
Se passar, commita e escolhe o próximo
Se não passar, você acabou de descobrir a primeira dívida de tipo do projeto, e descobrir isso em um arquivo é bem melhor que descobrir em cinquenta 😀
Uma nota de contexto sobre versão, porque isso entra no plano: o TypeScript 7.0 estável foi anunciado pela Microsoft no blog oficial em 08/07/2026
Ele é um port nativo do compilador, apresentado como cerca de 10x mais rápido na checagem de tipos, e foi feito pra ser compatível com o comportamento de checagem e de linha de comando do 6.0, adotando os padrões novos do 6.0 e transformando em erro duro flags e construções que o 6.0 depreciou
Traduzindo pro seu caso: revisar as flags do tsconfig.json faz parte do plano de migração, não é detalhe pra depois
até o próximo post!
Perguntas frequentes
Preciso converter todos os arquivos .js de uma vez para migrar JavaScript para TypeScript?
Não, e tentar isso é o que mais quebra projeto no meio do caminho. Com allowJs ligado, arquivos .js e .ts convivem no mesmo projeto, então dá pra renomear em lotes pequenos (3 a 5 arquivos) e rodar o compilador entre um lote e outro.
Qual a diferença entre allowJs e checkJs no tsconfig?
O allowJs só permite que arquivos .js sejam importados dentro de um projeto com .ts e .tsx, sem checar tipo neles. Já o checkJs funciona junto com o allowJs e faz o compilador reportar erro nesses .js, o que equivale a colocar // @ts-check no topo de cada um deles.
Dá pra ter checagem de tipo em um arquivo JavaScript sem renomear ele para .ts?
Dá sim, de duas formas. Colocando // @ts-check na primeira linha do arquivo você liga a checagem naquele .js isolado, e usando comentário JSDoc você adiciona informação de tipo ao código sem precisar converter o arquivo pra .ts.
Quando faz sentido ligar noImplicitAny ou o modo strict na migração?
Só depois que o projeto já compila com allowJs e checkJs ligados, nunca no primeiro dia. O noImplicitAny emite erro sempre que o tipo seria inferido como any silenciosamente, e o strict é o guarda-chuva que liga esse e outros checks (como strictNullChecks e strictFunctionTypes) de uma vez.
Qual a diferença entre @ts-ignore e @ts-expect-error para suprimir erro de tipo?
O // @ts-ignore não faz nada se a linha seguinte não tiver erro, então ele pode ficar esquecido no código pra sempre sem avisar ninguém. Já o // @ts-expect-error reporta ‘Unused @ts-expect-error directive’ quando a supressão deixou de ser necessária, o que ajuda a limpar gambiarra depois que o tipo foi corrigido de verdade.
Existe alguma ferramenta pra automatizar a conversão de JavaScript para TypeScript?
Sim, o ts-migrate é a ferramenta open source citada no post, que pega um projeto JavaScript ou parcialmente TypeScript e devolve um projeto TypeScript que compila. Mesmo assim vale revisar o resultado com o Claude Code em vez de confiar que a conversão automática já saiu pronta.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
As diferenças de var, let e const
Como fazer redirecionamento com PHP
Neste artigo você vai aprender a como fazer redirecionamento com PHP, utilizaremos abordagens fáceis de entender e de aplicar Fala programador(a), beleza? Bora aprender mais […]
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação A popularidade da automação de processos com o n8n está em alta, principalmente […]
