TypeScript é uma linguagem que só existe em tempo de build. O Node.js não possui um interpretador de TypeScript em seu runtime, então todo arquivo .ts que um processo Node "executa" já foi transformado em JavaScript puro antes que o V8 o veja.
Esse único fato reformula a maior parte do que esta seção cobre: Noções Básicas de TypeScript no Node mostra configurações de tsconfig.json funcionais, e tsx vs tsc vs ts-node compara ferramentas específicas, mas nenhuma responde à pergunta subjacente a ambas - o que uma "toolchain TypeScript" realmente está fazendo e por que as peças são separáveis. Esta página é esse modelo mental, a estrutura sobre a qual o restante da seção é construída.
TypeScript no Node é uma camada de tempo de compilação acoplada a um runtime que só entende JavaScript, então toda escolha de toolchain é realmente uma escolha sobre quando e como os tipos são apagados.
Por Que Importa: Erros de tipo, incompatibilidades de resolução de módulos e surpresas do tipo "funciona em dev, mas não em prod" quase sempre remontam a duas ferramentas discordando sobre como o código-fonte se torna JavaScript.
Conceitos Chave:type erasure (apagamento de tipos), type checking (verificação de tipos), transpilation (transpilação), module resolution (resolução de módulos), source maps (mapas de código-fonte), declaration files (arquivos de declaração).
Quando Usar: Ao escolher uma toolchain de desenvolvimento vs. produção, depurando por que um erro de tipo não impediu um deploy, decidindo sobre uma estratégia de module/moduleResolution no tsconfig.json, ou ao integrar uma equipe acostumada a um fluxo de trabalho TypeScript focado no navegador.
Limitações / Trade-offs: Transformações rápidas em desenvolvimento ganham velocidade ao pular a verificação de tipos, o que significa que um build quebrado ainda pode ser executado localmente até que o CI ou tsc o capture.
Tópicos Relacionados: resolução de módulos, tipagem gradual, validação em runtime nas fronteiras, remoção nativa de tipos do Node.
Todo fluxo de trabalho TypeScript realiza dois trabalhos logicamente separados: type checking (verificação de tipos), que valida seu código em relação aos tipos que você escreveu e relata erros, e transpilation (ou "compilação"), que remove esses tipos e emite JavaScript executável.
Nada exige que a mesma ferramenta faça ambos, e no ecossistema do Node eles rotineiramente não o fazem. tsc, o compilador oficial do TypeScript, pode fazer ambos simultaneamente, mas muitas equipes o usam apenas para verificação de tipos (tsc --noEmit) e deixam uma ferramenta separada e mais rápida lidar com a saída real em JavaScript.
Essa separação existe porque a verificação de tipos é comparativamente lenta - ela precisa construir uma compreensão completa dos tipos do seu programa - enquanto a remoção de anotações é comparativamente barata, mais próxima de excluir texto do que de analisá-lo. Uma analogia útil: a verificação de tipos é um revisor validando o argumento de um ensaio, enquanto a transpilação é um datilógrafo removendo suas notas de margem editoriais antes de imprimir. Você pode contratar duas pessoas diferentes para esses trabalhos, e a maioria das configurações de desenvolvimento Node rápidas faz exatamente isso - uma transformação leve (tsx, esbuild, SWC) remove os tipos dinamicamente para velocidade, enquanto tsc ou o serviço de linguagem do seu editor faz a revisão real, muitas vezes em uma programação separada e mais lenta.
function greet(name: string): string { return `Hello, ${name}`;}// Após o apagamento, o Node executa apenas:// function greet(name) { return `Hello, ${name}`; }
Três preocupações distintas interagem sempre que o código TypeScript chega a um processo Node, e confundir essas preocupações é onde a maior parte da confusão começa.
Type checking (verificação de tipos) ocorre contra os tipos, não os valores em runtime - ele só pode capturar o que suas anotações descrevem, e não tem efeito algum no comportamento do JavaScript emitido. Uma execução de tsc --noEmit que falha ainda deixa sua saída dist/ intocada; a verificação de tipos é puramente consultiva, a menos que algo (CI, um hook git, seu editor) esteja configurado para bloquear com base nela.
Module resolution (resolução de módulos) é onde o mundo em tempo de compilação do TypeScript precisa concordar com as regras de carregamento do runtime do Node, e isso é uma fonte frequente de atrito porque os dois evoluíram de forma um tanto independente. A configuração moduleResolution: "NodeNext" do TypeScript diz ao compilador para resolver importações usando as mesmas regras que o Node usa em runtime - respeitando o campo "type" do package.json, exigindo extensões de arquivo explícitas em importações no estilo ESM e honrando mapas de exports - para que o que é verificado em tempo de tipo localmente também seja resolvido quando o Node carregar o JavaScript emitido.
Declaration files (.d.ts) carregam apenas informações de tipo, nunca código em runtime, que é como um pacote npm publicado pode distribuir tipos sem distribuir código-fonte TypeScript - o arquivo .d.ts descreve a forma, o arquivo .js emparelhado é o que o Node realmente executa.
// tsconfig.json (excerto) - esta única configuração decide se// a resolução em tempo de compilação do TypeScript corresponde às regras de runtime do Node{ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext" }}// Configurações incompatíveis aqui são o motivo pelo qual uma importação pode ser verificada// em tempo de tipo sem problemas, mas lançar "Cannot find module" no momento em que o Node a executa.
O Node 24 adiciona uma complicação adicional que vale a pena nomear precisamente: ele pode executar arquivos .ts diretamente removendo anotações de tipo simples em tempo de carregamento, sem uma etapa de build separada. Isso é type erasure (apagamento de tipos) embutido no carregador de módulos do runtime, não type checking (verificação de tipos) - o Node exclui as anotações que reconhece e executa o que resta; ele não verifica se seus tipos são consistentes e rejeita sintaxe TypeScript que requer transformação real (como enum ou namespace merging) em vez de simples exclusão. tsx vs tsc vs ts-node cobre como isso se compara às ferramentas de desenvolvimento dedicadas com as quais ele se sobrepõe parcialmente.
Como a verificação e a emissão são separáveis, as equipes acabam escolhendo ferramentas diferentes para momentos diferentes no ciclo de vida de um projeto, e cada escolha troca velocidade por garantias.
Abordagem
Força
Fraqueza
Melhor Ajuste
tsc (compilar + verificar)
Única fonte de verdade; captura todos os erros de tipo antes da emissão
Opção mais lenta; não construída para loops de desenvolvimento iterativos rápidos
Etapa de build de produção, portão de CI
Transformação rápida (tsx, esbuild, SWC)
Inicialização quase instantânea; ótimas ergonomias em modo de desenvolvimento/observação
Não realiza verificação de tipos - tipos quebrados ainda são executados
Desenvolvimento local, executores de teste
Remoção nativa de tipos do Node
Nenhuma ferramenta para scripts simples; nada para instalar
Apenas apagamento, e rejeita anotações que requerem transformação real
Scripts, protótipos, serviços simples sem recursos TS complexos
Serviço de linguagem do editor
Feedback contínuo e inline enquanto você digita
Apenas tão preciso quanto os arquivos/configurações abertos do projeto; não é um portão de build
Criação diária, não aplicação em CI
Essa divisão tem consequências arquitetônicas reais. Um serviço pode passar em todos os testes locais com tsx enquanto envia um erro de tipo diretamente para a produção, se nada no pipeline chamar tsc em modo de verificação - é por isso que a maioria dos setups Node/TypeScript maduros executa uma transformação rápida para velocidade de iteração e uma etapa dedicada de tsc --noEmit no CI, em vez de confiar em apenas um deles.
O mesmo princípio de apagamento se estende além da fronteira da linguagem: como os tipos desaparecem em runtime, o TypeScript pode descrever a forma que você espera de um corpo de requisição, uma variável de ambiente ou a resposta de uma API de terceiros, mas não pode verificar se essa forma realmente chegou - essa é uma preocupação de runtime, não de tempo de compilação. Zod nas Fronteiras cobre o emparelhamento de tipos em tempo de compilação com validação em runtime exatamente nessas bordas, e Compartilhando Tipos com o Frontend cobre o problema relacionado de manter um contrato em tempo de compilação sincronizado entre dois processos implantados separadamente.
"Node executa TypeScript." Node executa JavaScript; todo caminho do código-fonte .ts para um processo em execução passa por uma etapa de apagamento ou compilação primeiro, mesmo quando essa etapa é invisível (como o stripping embutido do Node 24).
"Se tsx executa meu arquivo sem erro, meus tipos estão corretos."tsx e transformações similares removem tipos sem verificá-los - um arquivo com erros de tipo reais ainda pode ser executado sem problemas sob uma transformação rápida.
"A resolução de módulos do TypeScript apenas espelha como o Node carrega arquivos." Eles são implementados independentemente e podem discordar; moduleResolution: "NodeNext" existe especificamente para fazer o modelo do compilador corresponder ao comportamento do runtime do Node, em vez de assumi-lo.
"Arquivos de declaração (.d.ts) contêm código em runtime." Eles descrevem apenas tipos - um pacote pode distribuir arquivos .d.ts sem nenhum código-fonte TypeScript correspondente, puramente para descrever uma API JavaScript já compilada.
"A remoção nativa de tipos do Node significa que não preciso mais de tsc?" O stripping exclui anotações; ele não as valida e falha em recursos do TypeScript que precisam de transformação real (enums, namespace, etc.), então ele complementa tsc para scripts rápidos em vez de substituí-lo para projetos reais.
Por que o Node.js simplesmente não suporta TypeScript diretamente?
TypeScript é um superconjunto de JavaScript definido inteiramente por seu sistema de tipos, e os tipos são um conceito apenas de tempo de compilação - não há nada sobrando para um motor JavaScript como o V8 "executar" depois que os tipos são removidos, então construir suporte a TypeScript no Node significaria construir um compilador no runtime em vez de adicionar uma nova capacidade de execução.
Qual é a diferença real entre type checking e transpilation?
Type checking analisa seu código em relação aos seus tipos declarados e relata incompatibilidades sem alterar a saída; transpilation remove esses tipos (e faz o downleveling de sintaxe mais nova, se necessário) para produzir JavaScript executável, independentemente de a verificação ter passado.
Posso executar TypeScript no Node sem nenhuma etapa de build?
Sim, de duas maneiras: uma transformação de desenvolvimento rápida como tsx remove tipos dinamicamente sem um build separado, ou o stripping nativo de tipos do Node 24 faz o mesmo para anotações simples sem nenhuma ferramenta instalada - nenhuma delas verifica seus tipos, no entanto.
O stripping de tipos embutido do Node substitui `tsc`?
Não - ele apenas exclui a sintaxe de tipo que reconhece como segura para apagar; ele não valida tipos e falha em recursos do TypeScript que precisam de transformação real (enums, namespace, etc.), então ele complementa tsc para scripts rápidos em vez de substituí-lo para projetos reais.
Por que meu código é verificado em tempo de tipo localmente, mas falha ao importar em runtime?
Geralmente uma incompatibilidade de resolução de módulos - o compilador TypeScript resolveu a importação usando suas próprias configurações (que podem diferir das regras reais do runtime do Node), então o código satisfaz o verificador de tipos, mas o especificador não resolve da mesma forma quando o carregador de módulos do Node é quem o interpreta.
Para que servem realmente os arquivos de declaração (`.d.ts`)?
Eles permitem que um pacote JavaScript (ou um pacote TypeScript após a compilação) descreva seus tipos separadamente de seu código em runtime, para que os consumidores obtenham verificação de tipos e autocompletar no editor sem que o pacote precise distribuir ou mesmo conter código-fonte TypeScript.
É seguro pular a verificação de tipos em CI se meu editor não mostra erros?
Não - o serviço de linguagem de um editor reflete os arquivos atualmente abertos e seu próprio contexto de projeto, que pode se desviar de um build limpo e do zero; uma etapa dedicada de tsc --noEmit (ou equivalente) em CI é o único portão confiável porque verifica todo o projeto da mesma forma a cada vez.
Por que algumas configurações de Node TypeScript usam duas ferramentas diferentes em vez de apenas `tsc`?
Porque tsc faz a verificação e a emissão juntas, e a verificação é a parte lenta - dividir as duas permite que uma transformação rápida lide com ciclos de desenvolvimento/teste iterativos, enquanto uma etapa separada e mais lenta de tsc é executada com menos frequência (pré-commit, CI) puramente para validação.
O TypeScript afeta o comportamento do runtime do Node?
Não - uma vez que os tipos são apagados, o JavaScript emitido se comporta exatamente como se tivesse sido escrito diretamente em JavaScript; TypeScript muda quais erros são pegos antes da execução, não como o código executa quando o faz.
O que `moduleResolution: "NodeNext"` realmente muda?
Ele diz ao compilador TypeScript para resolver especificadores import/require usando o mesmo algoritmo que o Node usa em runtime - respeitando o campo "type" do package.json, mapas de exports e extensões explícitas - para que uma verificação de tipo bem-sucedida seja um sinal muito mais forte de que o JavaScript equivalente realmente será carregado.
Se os tipos são apagados, por que se preocupar com TypeScript estrito?
Porque o valor está inteiramente no tempo de autoria - tipos estritos capturam uma grande classe de bugs (tratamento de nulos, formas incompatíveis, tipos de argumento incorretos) antes que o código seja executado, o que é estritamente mais cedo e mais barato do que capturar os mesmos bugs em produção.