Executando Scripts TypeScript
Diferentes maneiras de executar arquivos TypeScript sem uma etapa manual de compilação - tsx, ts-node, o stripping de tipos nativo do Node 22 e o pipeline tradicional tsc + node.
Busque em todas as páginas da documentação
Diferentes maneiras de executar arquivos TypeScript sem uma etapa manual de compilação - tsx, ts-node, o stripping de tipos nativo do Node 22 e o pipeline tradicional tsc + node.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
# Opção moderna mais rápida - tsx (esbuild por baixo dos panos)
npx tsx script.ts
# Modo de observação com tsx
npx tsx watch script.ts
# Opção clássica - ts-node
npx ts-node script.ts
# Stripping de tipos nativo do Node.js (Node 22.6+)
node --experimental-strip-types script.ts
# Compilar e depois executar (melhor para produção)
npx tsc
node dist/script.js// package.json
{
"name": "ts-cli",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/script.ts",
"start": "tsx src/script.ts",
"build": "tsc",
"start:prod": "node dist/script.js"
},
"devDependencies": {
"tsx": "^4.19.0",
"typescript": "^5.6.0",
"@types/node": "^22.0.0"
}
}// tsconfig.json - configuração mínima de script Node.js
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"lib": ["ES2022"],
"types": ["node"]
},
"include": ["src/**/*.ts"]
}Quando usar isso: Qualquer script onde você queira argv tipado, configuração tipada, modelos de dados tipados ou tipos compartilhados com o código da sua aplicação.
Um CLI tipado que lê um arquivo JSON e imprime um resumo.
// src/summarize.ts
#!/usr/bin/env tsx
import { readFile } from 'node:fs/promises';
import { parseArgs } from 'node:util';
interface Order {
id: string;
total: number;
status: 'paid' | 'pending' | 'failed';
}
interface Summary {
count: number;
revenue: number;
paid: number;
}
const { values, positionals } = parseArgs({
options: {
verbose: { type: 'boolean', short: 'v' },
},
allowPositionals: true,
});
const file = positionals[0];
if (!file) {
console.error('Usage: summarize <orders.json> [--verbose]');
process.exit(1);
}
const raw = await readFile(file, 'utf8');
const orders = JSON.parse(raw) as Order[];
const summary: Summary = orders.reduce<Summary>(
(acc, o) => ({
count: acc.count + 1,
revenue: acc.revenue + o.total,
paid: acc.paid + (o.status === 'paid' ? 1 : 0),
}),
{ count: 0, revenue: 0, paid: 0 },
);
if (values.verbose) {
console.log(JSON.stringify(summary, null, 2));
} else {
console.log(`orders=${summary.count} revenue=${summary.revenue} paid=${summary.paid}`);
}# Execute-o
npx tsx src/summarize.ts ./orders.json --verboseO que isso demonstra:
interface tanto para os dados de entrada quanto para o resultado computadoreduce tipado com um argumento genérico explícitoawait de nível superior em um arquivo TypeScript ESMtsx para que o script seja executado diretamente quando executávelargv sem dependências com node:utilNenhum dos "runners TypeScript" faz type-checking em tempo de execução. Todos eles removem tipos e entregam o JavaScript resultante para o motor V8. As diferenças estão em como - e quão rápido - essa remoção acontece.
ts-node e ele suporta ESM, JSX e modo de observação prontos para uso.const enum, metadados de decorador).--experimental-strip-types é o loader embutido do Node.js (flag estável no Node 22.6+, sem flag no Node 23+). Ele deleta anotações de tipo através de uma transformação leve - ele não compila enums ou namespaces, e não faz type-checking.tsc + node emite arquivos JavaScript reais em outDir. É isso que você envia para produção porque elimina completamente a dependência do runner em tempo de execução.Como nenhum desses runners faz type-checking, emparelhe-os com tsc --noEmit em CI ou em um hook de pré-commit para garantir a segurança de tipos.
# tsx vs ts-node - flags diferentes, formato similar
npx tsx script.ts --flag
npx ts-node script.ts --flag
# tsx com modo de observação e um glob
npx tsx watch --clear-screen=false src/**/*.ts
# Stripping de tipos nativo do Node.js
node --experimental-strip-types script.ts
# Compilar e executar para imagens Docker de produção
tsc && node dist/script.js
# Executar tsx via shebang
# #!/usr/bin/env -S npx tsx// tsconfig.json mais rigoroso para scripts
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
"types": ["node"]
}
}Para scripts Node.js, as opções de compilador mais importantes são:
"target": "ES2022" - Node 18+ o suporta nativamente, então sem polyfills."module": "NodeNext" - corresponde à resolução de módulos real do Node, respeita "type" do package.json e força extensões .js nas importações."moduleResolution": "NodeNext" - o único modo de resolução que modela com precisão a lógica de pacotes duplos do Node."outDir": "dist" - onde tsc coloca o JavaScript compilado."types": ["node"] - inclui globais do @types/node como process, Buffer e __dirname (apenas CJS).Com NodeNext, especificadores de importação devem incluir a extensão .js final mesmo que você esteja escrevendo .ts. Isso parece estranho na primeira vez:
import { loadOrders } from './orders.js'; // refere-se a orders.ts"type": "module" estiver definido, arquivos .ts compilam para ESM e require lançará um erro. Se precisar de require, use createRequire(import.meta.url) ou renomeie para .cts.ts-node inicialização lenta. O compilador TypeScript completo pode adicionar 1–3 segundos por execução. Para scripts iterativos, use tsx em vez disso - seu pipeline baseado em esbuild geralmente inicia em menos de 100 ms.__dirname em TypeScript ESM. Ele só é definido em CJS. Em ESM, reconstrua-o: const __dirname = path.dirname(fileURLToPath(import.meta.url))..js. Com "module": "NodeNext" você deve escrever ./foo.js, não ./foo. O runner vê o arquivo .ts; o compilador TypeScript valida que o destino existe.tsx, ts-node --transpile-only e --experimental-strip-types todos pulam o type-checking por padrão. Execute tsc --noEmit em CI.--experimental-strip-types. Ele se recusa a lidar com sintaxe de enum, namespace e propriedade de parâmetro. Use objetos const ou tsx se precisar desses recursos.@types/node. Sem ele, process, Buffer e todo o namespace de módulo node:* aparecem como any, derrotando o modo estrito.| Ferramenta | Pontos Fortes | Pontos Fracos |
|---|---|---|
tsx | Rápido, amigável a ESM, modo de observação, suporte a JSX | Dependência extra |
ts-node | Usa o compilador real do TypeScript, maduro | Inicialização lenta, configuração ESM é complicada |
node --experimental-strip-types | Zero dependências, incluído com Node 22+ | Sem enums, sem type-check, flag mais recente |
bun run | TS nativo, muito rápido, bundler embutido | Runtime diferente, algumas lacunas na API do Node |
deno run | Seguro por padrão, TS nativo, stdlib | Modelo de resolução de módulos diferente |
swc-node | Velocidade impulsionada por SWC | Comunidade menor, menos recursos |
esbuild-register | Hook de require mínimo e rápido | Foco em CJS, não ideal para ESM |
npx tsx script.ts. tsx usa esbuild e geralmente inicia em bem menos de 100 ms - uma ordem de magnitude mais rápido que ts-node.
Não. tsx remove tipos com esbuild e executa o JavaScript. Emparelhe-o com tsc --noEmit em CI ou em um hook de pré-commit se quiser garantias.
tsx é um loader baseado em esbuild otimizado para velocidade e suporte a ESM. ts-node usa o compilador real do TypeScript, que é mais lento, mas emite exatamente o que tsc emitiria - incluindo enums e metadados de decorador.
É uma flag do Node.js (estável no Node 22.6+) que remove anotações de tipo TypeScript na hora antes de entregar o arquivo para o V8. Ele não compila enums ou namespaces, e não faz type-checking.
Sim, desde que o arquivo seja ESM - "type": "module" em package.json ou uma extensão .mts - e "target" seja pelo menos ES2022.
Com "module": "NodeNext", o TypeScript impõe as regras reais de resolução ESM do Node, que exigem a extensão exata do arquivo como ela aparece em tempo de execução. Em tempo de execução, esse arquivo será ./foo.js (após a compilação) ou o loader mapeará ./foo.js de volta para ./foo.ts - de qualquer forma, a importação deve terminar em .js.
Porque ele inicia o compilador real do TypeScript a cada execução. Use ts-node --transpile-only para pular o type-checking, ou mude para tsx, que usa esbuild.
target: ES2022, module: NodeNext, moduleResolution: NodeNext, strict: true, e types: ["node"]. Juntas, elas alinham a visão do TypeScript com a resolução de módulos real do Node.js e fornecem tipagens completas para os built-ins.
process.argv já é tipado como string[]. Para flags, use parseArgs de node:util - seu tipo de retorno genérico se estreita para o seu objeto de opções, então values.verbose retorna como boolean | undefined.
Use #!/usr/bin/env -S npx tsx ou instale tsx globalmente e use #!/usr/bin/env tsx. A flag -S permite que env divida os argumentos para que npx tsx seja tratado como um único comando.
JavaScript compilado. Executar tsc durante sua build produz arquivos .js simples em dist/, eliminando a dependência do runner e tornando a inicialização o mais rápida possível. Use tsx ou ts-node apenas para desenvolvimento.
Reconstrua-o a partir de import.meta.url:
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥