Ejecutar scripts de TypeScript
Diferentes formas de ejecutar archivos TypeScript sin un paso de compilación manual - tsx, ts-node, eliminación de tipos nativa de Node 22 y la pipeline tradicional tsc + node.
Busca en todas las páginas de la documentación
Diferentes formas de ejecutar archivos TypeScript sin un paso de compilación manual - tsx, ts-node, eliminación de tipos nativa de Node 22 y la pipeline tradicional tsc + node.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida - lista para copiar y pegar.
# Opción moderna más rápida - tsx (esbuild bajo el capó)
npx tsx script.ts
# Modo watch con tsx
npx tsx watch script.ts
# Opción clásica - ts-node
npx ts-node script.ts
# Eliminación de tipos nativa de Node.js (Node 22.6+)
node --experimental-strip-types script.ts
# Compilar y luego ejecutar (mejor para producción)
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 - configuración mínima de scripts para 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"]
}Cuándo usarlo: Cualquier script donde quieras tipado para argv, configuración tipada, modelos de datos tipados o tipos compartidos con tu código de aplicación.
Un CLI tipado que lee un archivo JSON e imprime un resumen.
// 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}`);
}# Ejecútalo
npx tsx src/summarize.ts ./orders.json --verboseLo que esto demuestra:
interface tanto para los datos de entrada como para el resultado calculadoreduce tipado con un argumento genérico explícitoawait en un archivo TypeScript ESMtsx para que el script se ejecute directamente cuando sea ejecutablenode:utilNinguno de los "ejecutores de TypeScript" realmente verifica tipos en tiempo de ejecución. Todos eliminan tipos y entregan el JavaScript resultante al motor V8. Las diferencias están en cómo - y qué tan rápido - esa eliminación ocurre.
ts-node y soporta ESM, JSX y modo watch de forma nativa.const enum, metadatos de decoradores).--experimental-strip-types es el cargador integrado de Node.js (bandera estable en Node 22.6+, sin banderas en Node 23+). Elimina anotaciones de tipos a través de una transformación ligera - no compila enums o espacios de nombres, y no verifica tipos.tsc + node emite archivos JavaScript reales en outDir. Esto es lo que envías a producción porque elimina completamente la dependencia del ejecutor en tiempo de ejecución.Como ninguno de estos ejecutores verifica tipos, emparéjalos con tsc --noEmit en CI o un hook pre-commit para garantizar seguridad de tipos.
# tsx vs ts-node - banderas diferentes, forma similar
npx tsx script.ts --flag
npx ts-node script.ts --flag
# tsx con modo watch y un glob
npx tsx watch --clear-screen=false src/**/*.ts
# Eliminación de tipos nativa de Node.js
node --experimental-strip-types script.ts
# Compilar y luego ejecutar para imágenes Docker de producción
tsc && node dist/script.js
# Ejecuta tsx a través de un shebang
# #!/usr/bin/env -S npx tsx// tsconfig.json más estricto 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 de Node.js las opciones de compilador más importantes son:
"target": "ES2022" - Node 18+ lo soporta de forma nativa, sin polyfills."module": "NodeNext" - coincide con la resolución de módulos actual de Node, respeta "type" en package.json y obliga extensiones .js en importaciones."moduleResolution": "NodeNext" - el único modo de resolución que modela con precisión la lógica de dual-package de Node."outDir": "dist" - donde tsc coloca el JavaScript compilado."types": ["node"] - trae variables globales de @types/node como process, Buffer y __dirname (solo CJS).Con NodeNext, los especificadores de importación deben incluir la extensión final .js aunque estés escribiendo .ts. Esto se siente extraño la primera vez:
import { loadOrders } from './orders.js'; // se refiere a orders.ts"type": "module", los archivos .ts se compilan a ESM y require lanzará un error. Si necesitas require, usa createRequire(import.meta.url) o renombra a .cts.ts-node inicio lento. El compilador completo de TypeScript puede agregar 1–3 segundos por ejecución. Para scripts iterativos usa tsx en su lugar - su pipeline respaldado por esbuild típicamente inicia en menos de 100 ms.__dirname en TypeScript ESM. Solo está definido en CJS. En ESM, reconstruyelo: const __dirname = path.dirname(fileURLToPath(import.meta.url))..js. Con "module": "NodeNext" debes escribir ./foo.js, no ./foo. El ejecutor ve el archivo .ts; el compilador de TypeScript valida que el destino exista.tsx, ts-node --transpile-only y --experimental-strip-types todos omiten la verificación de tipos por defecto. Ejecuta tsc --noEmit en CI.--experimental-strip-types. Se rehúsa a manejar enum, namespace y sintaxis de propiedades de parámetros. Usa objetos const o tsx si necesitas esas características.@types/node. Sin él, process, Buffer y todo el espacio de nombres del módulo node:* aparecen como any, derrotando el modo strict.| Herramienta | Fortalezas | Debilidades |
|---|---|---|
tsx | Rápido, amigable con ESM, modo watch, soporte JSX | Dependencia adicional |
ts-node | Utiliza compilador TypeScript real, maduro | Inicio lento, setup ESM es complicado |
node --experimental-strip-types | Cero dependencias, incluido en Node 22+ | Sin enums, sin verificación de tipos, bandera más nueva |
bun run | TS nativo, muy rápido, bundler integrado | Runtime diferente, algunas brechas en Node API |
deno run | Seguro por defecto, TS nativo, stdlib | Modelo de resolución de módulos diferente |
swc-node | Velocidad impulsada por SWC | Comunidad más pequeña, menos características |
esbuild-register | Hook require mínimo y rápido | Enfoque CJS, no ideal para ESM |
npx tsx script.ts. tsx utiliza esbuild e típicamente inicia en mucho menos de 100 ms - un orden de magnitud más rápido que ts-node.
No. tsx elimina tipos con esbuild y ejecuta el JavaScript. Emparéjalo con tsc --noEmit en CI o un hook pre-commit si quieres garantías.
tsx es un cargador respaldado por esbuild optimizado para velocidad y soporte ESM. ts-node utiliza el compilador TypeScript real, que es más lento pero emite exactamente lo que tsc emitiría - incluyendo enums y metadatos de decoradores.
Es una bandera de Node.js (estable en Node 22.6+) que elimina anotaciones de tipo TypeScript al vuelo antes de entregar el archivo a V8. No compila enums o espacios de nombres, y no verifica tipos.
Sí, siempre que el archivo sea ESM - "type": "module" en package.json o una extensión .mts - y "target" sea al menos ES2022.
Con "module": "NodeNext" TypeScript aplica las reglas reales de resolución ESM de Node, que requieren la extensión de archivo exacta como aparece en tiempo de ejecución. En tiempo de ejecución ese archivo será ./foo.js (después de compilación) o el cargador asignará ./foo.js de vuelta a ./foo.ts - de cualquier forma la importación debe terminar en .js.
Porque ejecuta el compilador TypeScript real en cada ejecución. Usa ts-node --transpile-only para omitir la verificación de tipos, o cambia a tsx, que utiliza esbuild.
target: ES2022, module: NodeNext, moduleResolution: NodeNext, strict: true y types: ["node"]. Juntas estas alinean la visión de TypeScript del mundo con la resolución de módulos actual real de Node.js y te dan tipado completo para las funciones integradas.
process.argv ya está tipado como string[]. Para banderas, usa parseArgs de node:util - su tipo de retorno genérico se estrecha a tu objeto de opciones, así que values.verbose regresa como boolean | undefined.
Usa #!/usr/bin/env -S npx tsx o instala tsx globalmente y usa #!/usr/bin/env tsx. La bandera -S deja a env dividir los argumentos así que npx tsx se trata como un único comando.
JavaScript compilado. Ejecutar tsc durante tu compilación produce archivos .js simples en dist/, eliminando la dependencia del ejecutor y haciendo el inicio lo más rápido posible. Usa tsx o ts-node solo para desarrollo.
Reconstruyelo desde 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 actualización: 10 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥