tsconfig.json explicado: la configuración de TypeScript para Node 24

El archivo tsconfig.json le dice a TypeScript qué revisar, con qué rigor y qué JavaScript generar. Vemos qué crea tsc --init, armamos uno para un proyecto de Node 24 que se puede ejecutar sin compilar y comprobamos lo que cambian las opciones más importantes.

Lo que crea tsc –init

En una carpeta vacía, npx tsc --init genera este archivo con TypeScript 7.0.2. Lo copiamos tal cual:

npx tsc --init
{
  // Visit https://aka.ms/tsconfig to read more about this file
  "compilerOptions": {
    // File Layout
    // "rootDir": "./src",
    // "outDir": "./dist",

    // Environment Settings
    // See also https://aka.ms/tsconfig/module
    "module": "nodenext",
    "target": "esnext",
    "types": [],
    // For nodejs:
    // "lib": ["esnext"],
    // "types": ["node"],
    // and npm install -D @types/node

    // Other Outputs
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,

    // Stricter Typechecking Options
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,

    // Style Options
    // "noImplicitReturns": true,
    // "noImplicitOverride": true,
    // "noUnusedLocals": true,
    // "noUnusedParameters": true,
    // "noFallthroughCasesInSwitch": true,
    // "noPropertyAccessFromIndexSignature": true,

    // Recommended Options
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
  }
}

Es un buen punto de partida, pero piensa en una biblioteca para el navegador: genera declaraciones (declaration), mapas de código y la opción de JSX para React, y deja "types": [], sin los tipos de Node. Para un programa de Node conviene ajustarlo. Los comentarios con // están permitidos en este archivo, aunque no sean JSON estricto.

Un tsconfig.json para Node 24

El proyecto de ejemplo calcula el total de un carrito. Tiene esta estructura:

precios/
  package.json
  tsconfig.json
  src/
    index.ts
    precio.ts
{
  "compilerOptions": {
    // Qué JavaScript se genera y qué módulos se usan
    "target": "es2024",
    "module": "nodenext",

    // Dónde está el código y adónde va lo compilado
    "rootDir": "src",
    "outDir": "dist",

    // La revisión: estricta, y un poco más
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    // Para que Node 24 pueda ejecutar los .ts directamente
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "rewriteRelativeImportExtensions": true,

    // Los tipos de Node y nada más
    "types": ["node"],
    "skipLibCheck": true
  }
}
OpciónQué hace
targetQué versión de JavaScript genera. Node 24 entiende es2024 completo, así que tsc no tiene que traducir nada moderno.
module: "nodenext"Sigue las reglas de módulos de Node: con "type": "module" en package.json, los archivos son módulos ES.
rootDir, outDirDe dónde lee el código y dónde deja el JavaScript al compilar.
strictActiva a la vez las comprobaciones serias: null no cabe en cualquier tipo, nada de any implícito, y varias más. Siempre encendido.
noUncheckedIndexedAccessLeer lista[i] o objeto[clave] da T | undefined, porque ese elemento puede no existir.
noImplicitOverrideObliga a escribir override al redefinir un método de la clase madre.
erasableSyntaxOnlyProhíbe lo que no se puede «borrar» sin más, como enum: Node 24 ejecuta los .ts quitando los tipos y no sabe traducir eso.
verbatimModuleSyntaxExige import type para lo que solo es un tipo, para que quede claro qué import desaparece al ejecutar.
rewriteRelativeImportExtensionsPermite escribir import … from "./precio.ts" y, al compilar, lo cambia a ./precio.js.
types: ["node"]Carga los tipos de Node (paquete @types/node) y ninguno más.
skipLibCheckNo revisa los .d.ts de los paquetes: acelera y evita errores ajenos.

Y el código:

export interface Articulo {
  nombre: string;
  precio: number;
}

export function total(articulos: Articulo[], iva = 0.16): number {
  const subtotal = articulos.reduce((s, a) => s + a.precio, 0);
  return Math.round(subtotal * (1 + iva) * 100) / 100;
}
import { total, type Articulo } from "./precio.ts";

const carrito: Articulo[] = [
  { nombre: "Audífonos", precio: 1299.99 },
  { nombre: "Cable HDMI", precio: 189 },
];
const formato = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
console.log("Total con IVA:", formato.format(total(carrito)));
console.log("Primer artículo:", carrito[0]?.nombre);

Revisar, ejecutar y compilar

Con el tsconfig.json en la carpeta, tsc sin argumentos lo lee. --noEmit revisa los tipos sin generar nada; si no hay errores, no dice nada:

npx tsc --noEmit
tsc(sin errores)

Node 24 ejecuta el .ts directamente, quitando los tipos. Ojo: así no se revisan, por eso tsc --noEmit va antes:

node src/index.ts
salidaTotal con IVA: $1,727.23
Primer artículo: Audífonos

Para distribuir JavaScript, tsc sin --noEmit compila a dist/. El import a ./precio.ts se ha reescrito a ./precio.js, y los tipos han desaparecido:

npx tsc
ls dist
salidaindex.js
precio.js
import { total } from "./precio.js";
const carrito = [
    { nombre: "Audífonos", precio: 1299.99 },
    { nombre: "Cable HDMI", precio: 189 },
];
const formato = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
console.log("Total con IVA:", formato.format(total(carrito)));
console.log("Primer artículo:", carrito[0]?.nombre);

En package.json, los scripts dejan las tres órdenes a mano:

{
  "name": "precios",
  "private": true,
  "type": "module",
  "scripts": {
    "inicio": "node src/index.ts",
    "revisar": "tsc --noEmit",
    "compilar": "tsc"
  }
}

Lo que cambian tres opciones

strict. Sin él, un parámetro sin tipo es any y nadie se queja. Con él:

function doble(x) {
  return x * 2;
}
console.log(doble("tres"));
npx tsc --noEmit --strict false --locale es implicito.ts
tsc(sin errores)
npx tsc --noEmit --strict --locale es implicito.ts
tscimplicito.ts(1,16): error TS7006: El parámetro 'x' tiene un tipo 'any' implícitamente.

noUncheckedIndexedAccess. Leer una clave que no existe da undefined. Sin la opción, TypeScript lo deja pasar y el programa falla al ejecutar:

const precios: Record<string, number> = { webcam: 899, bocina: 649.5 };
const p = precios["teclado"];
console.log(p.toFixed(2));
npx tsc --noEmit --strict --locale es indice.ts
tsc(sin errores)
node indice.ts
salidafile:///w/tsconfig/demos/indice.ts:3
console.log(p.toFixed(2));
              ^

TypeError: Cannot read properties of undefined (reading 'toFixed')

Node.js v24.22.0
npx tsc --noEmit --strict --noUncheckedIndexedAccess --locale es indice.ts
tscindice.ts(3,13): error TS18048: "p" es posiblemente "undefined".

verbatimModuleSyntax. Importar una interfaz como si fuera un valor es un error, porque al ejecutar esa interfaz no existe:

import { Articulo } from "./tipos.ts";
const a: Articulo = { nombre: "Webcam", precio: 899 };
console.log(a);
npx tsc --noEmit --strict --module nodenext --verbatimModuleSyntax --allowImportingTsExtensions --locale es importar.ts
tscimportar.ts(1,10): error TS1484: “Articulo” es un tipo y debe importarse mediante una importación de solo tipo cuando “verbatimModuleSyntax” está habilitado.

La solución es import type { Articulo }, o import { total, type Articulo } si en la misma línea hay valores y tipos, como en index.ts.

tsc con un archivo ignora el tsconfig.json. Si escribes npx tsc --noEmit src/index.ts en una carpeta que tiene tsconfig.json, TypeScript 7 no lo usa y te avisa con un error: error TS5112: tsconfig.json está presente, pero no se cargará si se especifican archivos en la línea de comandos. Use '--ignoreConfig' para omitir este error. Para revisar el proyecto, npx tsc --noEmit sin nombres de archivo.

Todo el código de esta página se comprobó con TypeScript 7.0.2 en modo estricto (--strict --locale es) y se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; los mensajes de error y las salidas están copiados de esa ejecución. Documentación oficial: TSConfig Reference.