Hay un problema que tengo con los posts de anuncios de compiladores: los números que cita el equipo oficial viven en condiciones de laboratorio que no se parecen a nada de lo que corrés en producción. Microsoft dice que TypeScript 7 es "a menudo 10x más rápido". Puede ser. Pero ¿en qué tipo de código? ¿Con qué flags? ¿En qué hardware?
Así que armé typescript7-demo — un lab público con benchmarks que cualquiera puede reproducir, con repos reales como sujetos de prueba, commits pineados, y dos flujos de GitHub Actions que podés fork-ear y correr hoy mismo.
Este post es el resumen de lo que aprendí construyendo eso, no solo de correrlo.
La primera trampa: el nombre del paquete
Empecemos con algo que perdería tiempo a cualquiera. TypeScript 7 no se instala como typescript@beta. El paquete publicado es @typescript/native-preview, y el binario que ejecutás es tsgo, no tsc. Mientras tanto, TypeScript 6 sigue siendo typescript pero para correr side-by-side existe @typescript/typescript6, que expone tsc6.
Eso no es un detalle menor. Si instalás typescript@beta en abril 2026, probablemente obtenés TypeScript 6 en alguna versión candidata. El package.json del repo lo deja explícito:
// package.json — instalación side-by-side correcta
{
"devDependencies": {
"@typescript/native-preview": "^7.0.0-dev.20260421.2",
"@typescript/typescript6": "^6.0.1",
"typescript": "^6.0.3"
},
"scripts": {
// tsc6 compila con el compilador JS de siempre
"typecheck:ts6": "tsc6 --noEmit",
// tsgo es el compilador nativo en Go
"typecheck:ts7": "tsgo --noEmit"
}
}
Con esa base, los dos compiladores corren sobre el mismo proyecto, comparables, sin tocarse.
Los números que obtuve
Si me preguntás qué me sorprendió más, es que la ganancia de TypeScript 7 no es uniforme. Depende dramáticamente del tipo de tipos que usés.
Los datos de site/data/history.json del commit analizado son los siguientes:
| Corpus | TS6 mediana | TS7 mediana | Delta |
|---|---|---|---|
| template-literal-stress | 44.009 ms | 17.097 ms | 2.57x |
| many-modules | 3.468 ms | 858 ms | 4.04x |
| project-references | 1.487 ms | 622 ms | 2.39x |
| type-fest v5.6.0 (real) | 125.026 ms | 76.685 ms | 1.63x |
| ts-pattern v5.9.0 (real) | 5.294 ms | 2.795 ms | 1.89x |
| ts-essentials v9.4.2 (real) | 1.369 ms | 1.164 ms | 1.18x |
Eso es lo que producen los scripts benchmark-synthetic.mjs y benchmark-public-repos.mjs corridos localmente. No son mis afirmaciones: son números del JSON comprometido en el repo, reproducibles.
Lo que me llama la atención: el corpus sintético many-modules — 2600 archivos encadenados con imports — alcanza 4x de mejora. Pero type-fest, que es exactamente la clase de código donde esperarías el mayor impacto (tipos condicionales, recursivos, mapped, template-literal todos juntos), sale en 1.63x. No es malo, pero es bastante lejos del 10x del anuncio.
Mi lectura: la ganancia nativa es real, y es mayor donde el cuello de botella es I/O y resolución de módulos. En tipos recursivos profundos, el algoritmo de inferencia sigue siendo el mismo — solo corre en Go en vez de en Node. Eso explica el gap entre 4x y 1.6x.
Cómo está armado el benchmark para que sea creíble
Esto importa más que los números: ¿por qué debería confiar en estos resultados y no en cualquier otro post que corra time tsc una vez?
El benchmark-public-repos.mjs clona repos en commits específicos y verifica el hash esperado antes de correr nada:
// scripts/benchmark-public-repos.mjs — verificación de integridad antes de medir
const projects = [
{
id: "type-fest",
repo: "sindresorhus/type-fest",
ref: "v5.6.0",
// si el commit no matchea, el benchmark falla antes de correr
expectedCommit: "a5491644b32160f804dd10d0b44dad461037f4c1",
// el comando exacto, no un npm script que podría cambiar
ts6: ["node", "--max-old-space-size=6144",
"node_modules/@typescript/typescript6/bin/tsc6",
"-p", "tsconfig.json", "--noEmit"],
ts7: ["node",
"node_modules/@typescript/native-preview/bin/tsgo.js",
"-p", "tsconfig.json", "--noEmit"],
},
// ... más repos con el mismo patrón
];
El benchmark sintético genera los proyectos en .tmp/synthetic-corpus — podés inspeccionarlos después de correr. No son una caja negra: son archivos TypeScript reales que podés abrir y verificar. Y los resultados salen como JSON primero, de los cuales se derivan el Markdown y el site.
Lo que no mide: latencia del editor, performance en runtime, y bundlers (a menos que lo configurés explícitamente). El docs/benchmark-methodology.md lo dice sin rodeos, lo cual me parece honesto.
La parte que más me interesó: la fricción de migración
Los benchmarks son el gancho. Pero el valor real para un equipo que tiene que tomar una decisión ahora está en el scanner de migración.
El scripts/scan-migration.mjs lee cada tsconfig*.json del proyecto y reporta lo que va a romperse. Los tres errores que vi reportados más seguido en repos reales:
moduleResolution=node10 — removido en TypeScript 7. Si tenés esto, necesitás migrar a node16, nodenext, o bundler y verificar que tu resolución de package.json exports siga funcionando igual.
baseUrl — removido en las builds preview de TypeScript 7. Esto me parece el más doloroso en repos grandes, porque baseUrl fue la solución estándar para imports absolutos antes de que paths fuera cómodo. Hay proyectos heredados que tienen decenas de imports que dependen de esto.
moduleResolution=classic — incompatible con cualquier path moderno. Si lo tenés en 2026, tenés un problema más grande que TypeScript 7.
El scanner también emite info para skipLibCheck (que puede ocultar problemas durante migraciones) y para la ausencia de isolatedDeclarations cuando declaration: true está activo. Ese segundo me parece particularmente útil porque isolatedDeclarations es una dirección clara del ecosistema TypeScript, no solo una feature de TypeScript 7.
El fixture fixtures/isolated-declarations/bad-export.ts lo demuestra de forma concreta:
// fixtures/isolated-declarations/bad-export.ts
// Esto falla con isolatedDeclarations: true porque
// el tipo de retorno no está declarado explícitamente.
// tsgo va a rechazar esto; tsc6 con isolatedDeclarations también.
export const getPostMetadata = async (slug: string) => {
return {
slug,
title: "missing explicit return type",
};
};
El test en test/tooling.test.mjs verifica que tsgo rechaza ese archivo con el error correcto. Es un ejemplo pequeño pero ejecutable.
GitHub Actions sin exponer código privado
Este fue el punto de diseño que más pensé. Si querés testear TypeScript 7 en tu propio repo sin publicar el código, el repo genera un workflow de GitHub Actions que podés copiar y correr dentro de tu entorno privado.
El workflow typescript-7-open-source.yml corre en cada push a main y en PRs. El typescript-7-full-benchmark.yml es manual o semanal (los lunes a las 10 UTC), y acepta inputs para controlar RUNS y WARMUPS. Los artefactos — benchmark-results.json, migration-findings.json — se guardan aunque el job falle, que es exactamente lo que querés cuando estás investigando por qué TypeScript 7 rechaza algo.
Ambos workflows tienen permissions: contents: read y nada más. Sin escribir al repositorio, sin tokens extras, sin sorpresas.
Lo que no me cierra del estado actual
Voy a ser directo en algunas cosas:
Los benchmarks sintéticos se corren con runs: 1 y warmups: 0 en los resultados que tengo commiteados. Eso lo dice el JSON: "runs": 1. El benchmark-methodology.md dice explícitamente que preferís medianas cuando hay múltiples muestras, pero el resultado más visiblemente compartido (el history.json) tiene exactamente una muestra por punto de dato. Para los sintéticos, eso hace que el delta de 4x en many-modules sea una observación de una sola corrida en mi máquina Windows. Reproducible, sí. Estadísticamente robusto, no del todo.
El workflow de CI usa runs: 3 y warmups: 1 por defecto, que está mejor. Pero para los números que commiteé localmente, aplica la advertencia de "sanity check" que el propio benchmark-methodology.md le da a las corridas únicas.
Eso no invalida el lab — invalida la certeza de los números específicos. La metodología, el diseño, los repos elegidos, el scanner de migración: todo eso sigue siendo sólido.
Mi postura
TypeScript 7 va a importar más que la mayoría de las actualizaciones de compilador que vimos en los últimos cinco años. La base nativa en Go no es marketing: cambia el techo de lo que es computable antes de que el feedback loop se haga inaceptable en repos grandes. Para mí eso importa en contextos como juanchi.dev (que es un proyecto relativamente chico) pero sobre todo en codebases tipo enterprise con muchos packages y project references — que es exactamente el mundo en el que trabajo en Lakaut.
Lo que no compro: que esto sea urgente hoy para la mayoría de los equipos. TypeScript 7 todavía es beta. Los flags --checkers, --builders, --singleThreaded son preview behavior. baseUrl removido va a romper bastante código heredado. La historia correcta es: armá el lab ahora, corré el scanner de migración, identificá tus blockers, y no migrés todavía.
Usar este repo para medir, sí. Para hacer un npm install @typescript/native-preview en producción esta semana, no.
Si lo corrés en tu propio proyecto y los números son distintos a los míos, eso es información útil. El .github/ISSUE_TEMPLATE/typescript-7-result.yml tiene exactamente el formato que necesitás para reportarlo.
Este artículo fue publicado originalmente en juanchi.dev
Top comments (0)