Entre junio de 2024 y febrero de 2025 TypeScript publicó cuatro versiones: 5.5, 5.6, 5.7 y 5.8. No todas tienen el mismo peso, pero cada una trae algo concreto que afecta al día a día. Aquí va lo que importa de cada una.
TypeScript 5.5: predicados de tipo inferidos e isolatedDeclarations
Filtros que ya no necesitan anotación manual
Antes de la 5.5, esto no funcionaba como esperabas:
const valores = [1, null, 3, null, 5];
const sinNulos = valores.filter(x => x !== null);
// Tipo inferido: (number | null)[] ? TypeScript no "veía" el filtro
Para que TypeScript lo entendiese tenías que escribir el predicado a mano:
const sinNulos = valores.filter((x): x is number => x !== null);
Desde la 5.5, TypeScript infiere el predicado automáticamente cuando la función de filtro descarta un tipo específico. El tipo resultante es ya number[] sin que tengas que hacer nada. Es un alivio en código que trabaja mucho con arrays nullable.
isolatedDeclarations: generar .d.ts sin leer todo el proyecto
Esta opción es menos vistosa pero técnicamente importante, sobre todo si usas herramientas de build rápidas como esbuild u oxc-transform.
El problema hasta ahora era que generar archivos .d.ts requería que TypeScript procesase todo el grafo de dependencias del proyecto. Con isolatedDeclarations: true, puedes generar el .d.ts de cada fichero de forma independiente, sin necesitar el resto. El compilador te fuerza a anotar explícitamente los tipos públicos, lo que hace posible que herramientas externas generen las declaraciones sin ejecutar tsc.
Si no estás construyendo una librería o no usas estas herramientas, no te cambia nada. Si sí, es un cambio significativo en los tiempos de build.
${configDir} en rutas de tsconfig
Antes de 5.5, cuando configurabas paths en un tsconfig.json situado en un subdirectorio, las rutas se resolvían desde el directorio de trabajo, no desde donde estaba el propio tsconfig. Esto causaba problemas en monorepos con múltiples tsconfigs anidados.
Ahora puedes escribir:
{
"compilerOptions": {
"paths": {
"@/*": ["${configDir}/src/*"]
}
}
}
Y la ruta es relativa al fichero tsconfig.json, sea donde sea que esté. Pequeño cambio, bastante útil en proyectos con estructura compleja.
TypeScript 5.6: iteradores tipados y detección de tautologías
Iterator helpers con tipos correctos
La propuesta de Iterator Helpers de TC39 lleva tiempo madurando, y ES2025 la incluye. TypeScript 5.6 se alinea con ella: ahora comprende y tipa correctamente métodos como .map(), .filter() o .take() sobre iteradores nativos.
Antes, trabajar con iteradores que no eran arrays perdía el tipado en cuanto usabas estos métodos. Ahora el tipo se propaga correctamente a través de la cadena.
Condiciones que siempre son verdaderas (o falsas)
TypeScript 5.6 añade detección de expresiones booleanas que no pueden cambiar de valor. Por ejemplo:
if (x > 0 === true) { ... }
Comprar un boolean con === true siempre es verdad si x > 0 es verdad, y por tanto la condición es una tautología. TypeScript ahora avisa de esto porque suele ser un bug o un despiste al refactorizar. Lo mismo con expresiones como if (typeof x === "string" || typeof x === "number" || true), donde el || true hace que la condición completa sea siempre verdadera.
No es un cambio que vayas a notar todos los días, pero cuando pilla un bug real, vale su peso en oro.
TypeScript 5.7: imports con extensión .ts y variables no inicializadas
El lío histórico de las extensiones en imports
Si alguna vez has configurado un proyecto TypeScript con "moduleResolution": "node16" o "bundler", habrás visto este comportamiento extraño: el archivo fuente es utils.ts, pero en el import tienes que escribir .js:
import { algo } from './utils.js'; // ? aunque el archivo sea utils.ts
La razón es que TypeScript emite .js y Node espera .js, así que TypeScript te obliga a escribir ya la extensión final. La 5.7 da un paso más: ahora puedes escribir la extensión .ts directamente y TypeScript la reescribe a .js en el output.
import { algo } from './utils.ts'; // TypeScript lo emite como './utils.js'
Menos confusión mental entre lo que escribes y lo que se emite.
Variables no inicializadas en ramas condicionales
TypeScript 5.7 mejora la detección de variables que pueden no estar asignadas cuando se usan:
let mensaje: string;
if (condicion) {
mensaje = "hola";
}
console.log(mensaje.toUpperCase()); // Error: 'mensaje' puede no estar asignada
Este tipo de error ya existía en TypeScript, pero la 5.7 lo detecta en más casos, especialmente dentro de estructuras de control más complejas. El análisis de flujo de control se vuelve más preciso.
TypeScript 5.8: require() de ESM y --erasableSyntaxOnly
Esta es la versión con más impacto de las cuatro, porque toca dos problemas que llevaban años siendo un dolor.
require() de módulos ESM desde CommonJS
Durante años, mezclar módulos ESM y CommonJS en Node.js ha sido una fuente constante de errores. Si tenías un paquete ESM y querías usarlo desde código CJS, te encontrabas con:
Error [ERR_REQUIRE_ESM]: require() of ES Module not supported
Node.js 22 introdujo soporte experimental para require() de módulos ESM. TypeScript 5.8 se alinea con esto: cuando el target de salida es compatible, TypeScript puede emitir código que usa esta nueva API. No es magia, y requiere Node.js 22 o superior con la feature activa, pero cierra un hueco que ha estado ahí desde que ESM se empezó a adoptar en serio.
--erasableSyntaxOnly: código que los runtimes pueden procesar sin tsc
Node.js 22.6+ añadió --experimental-strip-types, que ejecuta TypeScript directamente eliminando las anotaciones de tipo sin hacer ningún type checking. Bun lleva haciendo esto desde siempre. Deno también.
El problema es que no toda la sintaxis de TypeScript se puede simplemente "borrar". Los enum y los namespace generan código JavaScript real, no son solo anotaciones. Si tienes esto:
enum Color {
Rojo,
Verde,
Azul
}
Un runner que solo hace strip types no puede procesarlo correctamente, porque enum se convierte en un objeto JavaScript real durante la compilación.
La opción --erasableSyntaxOnly de TypeScript 5.8 soluciona esto desde la raíz: activa una comprobación que solo permite sintaxis que se puede eliminar sin ejecutar nada. Si tienes un enum, un namespace o un import type = require(...), TypeScript te da un error. Así te aseguras de que tu código es compatible con Node.js strip types, Bun y cualquier runner similar.
Lo que sí se puede borrar sin problema: anotaciones de tipo, interfaces, type aliases, modificadores de acceso (public, private, protected). Lo que no: enum, namespace, constructores de parámetros (constructor(private x: number) en algunos contextos), import type = require(...).
Checks más granulares en return
TypeScript 5.8 también mejora la verificación de tipos en expresiones de retorno complejas. Antes, si devolvías un ternario, TypeScript comprobaba el tipo del resultado final. Ahora comprueba cada rama por separado:
function valor(x: boolean): string {
return x ? procesarA() : procesarB();
// ^^^^^^^^^^^ ^^^^^^^^^^^
// Ahora TypeScript verifica cada rama independientemente
}
El resultado práctico es que detecta más errores en funciones con tipos de retorno complejos, especialmente cuando las ramas devuelven tipos que se solapan parcialmente.
El camino hacia los runtimes sin compilación
Hay un hilo conductor en las últimas versiones de TypeScript que vale la pena ver en conjunto: la tendencia hacia código que se puede ejecutar directamente sin pasar por tsc.
- Node.js 22.6+:
--experimental-strip-typesejecuta TypeScript directamente. - Node.js 23+: strip types activo por defecto (sin type checking, solo elimina las anotaciones).
- Bun: ejecuta TypeScript de forma nativa desde su primera versión.
- Deno: también soporta TypeScript sin compilación explícita.
--erasableSyntaxOnly es la respuesta de TypeScript a esta tendencia: una forma de garantizar, en tiempo de desarrollo, que tu código es compatible con todos estos runtimes. Actívalo y si algo no es borrable, te entera antes de llegar a producción.
Si tienes enum en tu código y quieres migrar, las alternativas más comunes son los const enum (aunque tienen sus propias limitaciones con isolatedDeclarations) o directamente union types de strings:
// En lugar de:
enum Color { Rojo = 'rojo', Verde = 'verde' }
// Puedes usar:
type Color = 'rojo' | 'verde';
const Color = { Rojo: 'rojo', Verde: 'verde' } as const;
Cómo actualizar
Para pasar a la versión más reciente:
npm install typescript@latest
Si quieres aprovechar las nuevas opciones desde ya, estas son las que merece la pena revisar en tu tsconfig.json:
"isolatedDeclarations": true: si construyes una librería o usas herramientas de build por fichero."erasableSyntaxOnly": true: si usas Node.js 22+ con strip types, Bun o Deno."moduleResolution": "bundler"o"node16": si quieres usar imports con extensión.ts.
Lo más importante antes de activar erasableSyntaxOnly: busca todos los enum y namespace en tu proyecto. Si los tienes y no los puedes migrar ahora mismo, no actives el flag todavía.
Para más contexto sobre cómo TypeScript ha evolucionado en los últimos años, puedes leer TypeScript en 2025: el contexto anterior y JavaScript y TypeScript: la evolución del tipado.
Imagen: Pexels / Negative Space
