
TypeScript 7.0 e 7.1 in profondità: parallelismo, nuovi default e migrazione
Approfondimento tecnico su TypeScript 7.0 e 7.1: come funzionano --checkers e --builders, perché cambiano i default, cosa è stato rimosso, esempi di migrazione e FAQ.
Nell’articolo introduttivo su TypeScript 7 abbiamo visto il quadro generale: compilatore riscritto in Go, build 8-12 volte più veloci, qualche default cambiato e un’API programmatica che arriverà solo con la 7.1. Qui scendiamo di un livello: come funziona il parallelismo e perché può cambiare i risultati, perché il team ha cambiato proprio quei default, cosa si rompe concretamente in un tsconfig.json esistente e come si sistema, e cosa c’è davvero nel piano della 7.1.
Un porting, non una riscrittura
Il punto più importante per capire TypeScript 7 è che il compilatore Go non è un nuovo type checker. Il team ha portato il codice esistente file per file, funzione per funzione, mantenendo la stessa struttura: stesso scanner, stesso parser, stesso binder, stesso checker. È anche il motivo principale per cui è stato scelto Go invece di Rust: la struttura del codice (grafi di oggetti con riferimenti ciclici, garbage collector, niente ownership esplicita) si traduce quasi riga per riga, cosa che in Rust avrebbe richiesto di riprogettare le strutture dati.
La conseguenza pratica è che la semantica dei tipi è la stessa di TypeScript 6. Se un tipo condizionale, un’inferenza o un narrowing funzionavano in 6.0, funzionano allo stesso modo in 7.0: il compilatore è stato validato contro l’intera test suite accumulata in oltre dieci anni. Le differenze che vedrai non vengono da “un checker diverso”, ma da tre fonti precise: i nuovi default, le opzioni rimosse, e il parallelismo.
Come funziona il parallelismo: --checkers
Il vecchio tsc aveva un solo type checker che analizzava tutti i file in sequenza. TypeScript 7.0 crea invece un numero fisso di type checker indipendenti (4 di default), ognuno con la propria “visione del mondo”: ogni worker riceve una porzione dei file del programma e li controlla in parallelo agli altri, condividendo in memoria l’AST già parsato.
# default: 4 checker in parallelo
npx tsc --noEmit
# macchina con molti core, codebase grande
npx tsc --noEmit --checkers 8
# tutto su un solo thread (debug, container con poca RAM)
npx tsc --noEmit --singleThreaded
Il compromesso è la memoria: ogni checker costruisce i propri tipi, quindi i tipi “comuni” (quelli di lib.d.ts, di React, di una libreria usata ovunque) vengono in parte calcolati più volte. Aumentare --checkers accelera i progetti grandi ma fa salire il consumo di RAM — su un runner CI con 2 core e 4 GB, salire oltre il default è spesso controproducente.
⚠ Attenzione
Dato lo stesso input, i file vengono sempre divisi tra i checker allo stesso modo e i risultati
sono deterministici. Ma cambiare il numero di checker può, in casi rari, far emergere
risultati dipendenti dall’ordine. Se il tuo team usa macchine diverse, fissa lo stesso
--checkers in locale e in CI (ad esempio nello script typecheck del package.json).
Perché stableTypeOrdering è sempre attivo
C’è un dettaglio poco visibile ma fondamentale per far funzionare il parallelismo. In TypeScript ≤ 6, ogni tipo riceveva un id numerico nell’ordine in cui veniva creato, e quell’id determinava ad esempio l’ordine dei membri di una union. Risultato: lo stesso tipo poteva essere stampato come string | number o number | string a seconda di quale file era stato controllato per primo.
Con un solo checker sequenziale questo era “stabile per caso”. Con quattro checker che lavorano in parallelo, l’ordine di creazione dei tipi non è più un dato affidabile. Per questo TypeScript 7.0 ordina i tipi secondo un criterio indipendente dall’ordine di controllo: stableTypeOrdering è true e non si può disattivare (in 6.0 era un’opzione per prepararsi alla migrazione).
// utils.ts
export function parse(input: string) {
return Number.isNaN(Number(input)) ? input : Number(input);
}
Il .d.ts generato contiene una union il cui ordine, in 7.0, non dipende più da cosa è stato controllato prima. Nella pratica ti accorgerai di questo cambiamento soprattutto in due punti: snapshot test che confrontano messaggi d’errore o file .d.ts generati, e diff rumorosi nelle dichiarazioni pubblicate da una libreria. Aggiorna gli snapshot una volta, e da lì resteranno stabili.
Project reference in parallelo: --builders
Nei monorepo con tsc --build, il secondo livello di parallelismo è --builders, che controlla quanti progetti referenziati vengono compilati contemporaneamente:
npx tsc --build --builders 4
A differenza di --checkers, variare il numero di builder non cambia i risultati. Il limite è il grafo delle dipendenze: se app dipende da ui che dipende da core, quei tre progetti restano sequenziali per forza. --builders rende davvero quando hai molti pacchetti “foglia” indipendenti tra loro.
I nuovi default, uno per uno (e perché)
L’idea di fondo è semplice: i default di TypeScript erano rimasti fermi a un ecosistema di dieci anni fa (ES5, CommonJS, script globali). I nuovi default riflettono come si scrive TypeScript oggi, in modo che un tsconfig.json minimo faccia la cosa giusta.
types: []
Prima, TypeScript includeva automaticamente ogni pacchetto @types/* presente in node_modules, anche quelli installati come dipendenza transitiva di qualcos’altro. Questo rallentava il caricamento del programma e causava conflitti tra dichiarazioni globali (il classico @types/jest contro @types/mocha che ridefiniscono entrambi describe). Ora nessun pacchetto @types globale viene incluso se non lo dichiari:
error TS2304: Cannot find name 'process'.
error TS2582: Cannot find name 'describe'. Do you need to install type definitions for a test runner?
La correzione è esplicitare i tipi globali che usi davvero:
{
"compilerOptions": {
"types": ["node", "vitest/globals"]
}
}
I pacchetti @types che importi esplicitamente (import express from "express" con @types/express) continuano a funzionare: types riguarda solo le dichiarazioni globali.
rootDir: "./"
Prima rootDir veniva dedotto come la directory comune a tutti i file sorgente. Sembra comodo, ma significava che la struttura di outDir poteva cambiare aggiungendo un file: basta un scripts/seed.ts fuori da src/ e l’output passa da dist/index.js a dist/src/index.js. Ora il default è la directory del tsconfig.json, e se i sorgenti stanno in src/ va detto:
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["./src"]
}
noUncheckedSideEffectImports: true
Gli import “solo per effetti collaterali” (import "./polyfills") non venivano verificati: se il file non esisteva, TypeScript taceva e l’errore compariva solo a runtime o al bundling. Ora vengono risolti come qualsiasi altro import:
import "./polyfils"; // errore: modulo non trovato (typo in "polyfills")
Se importi file CSS o altri asset in questo modo, serve una dichiarazione di modulo (i template di Vite e Astro la includono già):
// src/env.d.ts
declare module "*.css";
strict, module e target
strict: true era già la prima riga di quasi ogni tsconfig.json generato da un template, quindi per la maggior parte dei progetti non cambia nulla; lo nota chi aveva un config minimo o vuoto. module: esnext riflette il fatto che la destinazione tipica è un bundler o un runtime ESM. target punta ora all’ultima versione ECMAScript stabile invece che a ES5: nessun down-leveling di classi, async/await o optional chaining, a meno che non lo chiedi.
Opzioni rimosse: tabella di migrazione
Queste opzioni in 7.0 sono errori, non più avvisi di deprecazione:
| Prima (≤ 6.0) | In TypeScript 7 |
|---|---|
target: "es5", downlevelIteration |
target moderno; per ES5 serve un transpiler (Babel, SWC) |
moduleResolution: "node" / "node10" |
"bundler" (con bundler) o "nodenext" (Node puro) |
moduleResolution: "classic" |
"bundler" o "nodenext" |
module: "amd" / "umd" / "systemjs" |
"esnext" + bundler |
baseUrl |
paths con percorsi relativi al tsconfig.json |
esModuleInterop: false |
rimuovere l’opzione (è sempre attiva) |
alwaysStrict: false |
rimuovere l’opzione |
module Foo {} |
namespace Foo {} |
import data from "./x.json" assert { … } |
import data from "./x.json" with { type: "json" } |
Il caso più comune in progetti reali è baseUrl usato solo per gli alias. Prima e dopo:
// prima
{
"compilerOptions": {
"baseUrl": "./src",
"paths": { "@/*": ["*"] }
}
}
// dopo
{
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
}
}
Attenzione: baseUrl permetteva anche import “nudi” come import { x } from "utils/date" risolti rispetto a src/. Senza baseUrl quegli import vanno convertiti in alias espliciti (@/utils/date) o in percorsi relativi.
Template literal e Unicode
Un cambiamento di semantica vero, ma che corregge un bug di lunga data. Con infer sui template literal, TypeScript 6 divideva le stringhe per unità UTF-16, spezzando emoji e caratteri fuori dal Basic Multilingual Plane a metà:
type HeadTail<S> = S extends `${infer Head}${infer Tail}` ? [Head, Tail] : never;
type R = HeadTail<"😀abc">;
// TS 6.0: ["\ud83d", "\ude00abc"] — mezza emoji per parte
// TS 7.0: ["😀", "abc"]
Se hai tipi di utilità che iterano le stringhe carattere per carattere (lunghezza di una stringa a livello di tipo, validatori di formato), con input non-ASCII i risultati cambiano — nel verso corretto.
Progetti JavaScript: il supporto JSDoc è stato ripulito
Per chi usa TypeScript per controllare file .js tramite JSDoc, la 7.0 elimina una serie di pattern legacy che il vecchio compilatore accettava per compatibilità con Closure Compiler:
// ❌ non più supportati
/** @type {function(string): void} */ // sintassi stile Closure
/** @type {?} */ // "?" da solo come tipo
/** @enum {string} */ // gestione speciale di @enum
/** @param {Foo!} x */ // "!" postfisso
// ✅ equivalenti
/** @type {(s: string) => void} */
/** @type {any} */
/** @param {Foo} x */
Anche usare un valore dove serve un tipo non è più accettato: se Config è una variabile, in JSDoc va scritto typeof Config.
CLI, watch mode ed editor
Due dettagli pratici. Primo: tsc file.ts in una directory che contiene un tsconfig.json ora fallisce, perché era ambiguo se il config andasse applicato o no. Se vuoi davvero compilare un singolo file ignorando il config:
npx tsc --ignoreConfig scripts/one-off.ts
Secondo: la watch mode è stata ricostruita sul file watcher di Parcel (portato in Go), più stabile e meno affamato di CPU di quello basato su polling. Nell’editor, il nuovo language server riduce di oltre l’80% i comandi falliti e di oltre il 60% i crash rispetto a 6.0; in VS Code si attiva con l’estensione nativa e si può spegnere con il comando “Disable TypeScript 7 Language Server” se un progetto ha bisogno ancora di plugin basati sulla vecchia API.
Il buco dell’API e come convivere con 6.0
TypeScript 7.0 non espone un’API programmatica. Chiunque importi typescript come libreria — typescript-eslint, Volar per Vue, i plugin per Svelte, Astro, MDX, i template Angular, molti tool di code generation — deve restare su 6.0. Per questo esiste il pacchetto @typescript/typescript6 (binario tsc6), che si usa con gli alias npm:
{
"devDependencies": {
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2"
},
"scripts": {
"typecheck": "tsc -p . --noEmit --checkers 4"
}
}
In questa configurazione i tool che fanno require("typescript") ricevono la 6.0, mentre tu puoi usare il binario della 7.0 per il type-check veloce in CI. Due compilatori, due versioni: conviene tenerle allineate sulla stessa configurazione e considerare la 6.0 un “ponte” temporaneo.
Roadmap: da “Corsa” a TypeScript 7.1
Per collocare tutto nel tempo, ecco le tappe principali del progetto, dall’annuncio del porting nativo fino alle date pianificate per la 7.1:
| Data | Tappa | Cosa significa |
|---|---|---|
| Marzo 2025 | Annuncio del porting nativo (“Corsa”) | Il team presenta il compilatore in Go e l’obiettivo di build ~10 volte più veloci |
| Maggio 2025 | Native Previews | @typescript/native-preview su npm (binario tsgo) ed estensione di anteprima per VS Code |
| Dicembre 2025 | Aggiornamento sui progressi | Build dei project reference e language service vicini alla completezza |
| 11 febbraio 2026 | TypeScript 6.0 Beta | Ultima release basata sul codice JavaScript, pensata come “ponte” verso la 7.0 |
| 23 marzo 2026 | TypeScript 6.0 stable | Deprecazioni e flag come stableTypeOrdering per preparare la migrazione |
| 21 aprile 2026 | TypeScript 7.0 Beta | Il compilatore nativo entra nel pacchetto typescript |
| 18 giugno 2026 | TypeScript 7.0 RC | Comportamento considerato definitivo, si raccolgono solo bug critici |
| 8 luglio 2026 | TypeScript 7.0 stable (7.0.2) | Nuovi default, opzioni legacy rimosse, nessuna API programmatica |
| 6 ottobre 2026 | TypeScript 7.1 Beta (pianificata) | Prime API stabili (Content Mapper, Emit, Language Service), es2026 |
| 10 novembre 2026 | TypeScript 7.1 RC (pianificata) | Congelamento delle funzionalità |
| 24 novembre 2026 | TypeScript 7.1 stable (pianificata) | Migrazione possibile anche per Vue, Svelte, Astro, Angular e typescript-eslint |
Le date della 7.1 vengono dal piano di iterazione pubblicato su GitHub e possono ancora cambiare; tutte le altre sono date di rilascio effettive.
TypeScript 7.1: cosa c’è nel piano
La 7.1 ha beta prevista il 6 ottobre, RC il 10 novembre e stable il 24 novembre 2026. Il piano di iterazione pubblicato su GitHub si divide in quattro aree.
API stabili. È il cuore della release. Tre pezzi:
- Content Mapper API — permette a un tool di dire al compilatore “questo file
.vue/.astro/.sveltecorrisponde a questo codice TypeScript virtuale, con queste mappature di posizione”. È esattamente ciò che fanno oggi Volar e i plugin dei framework, e il motivo per cui non possono ancora passare a 7.0. - Emit API — per chi usa il compilatore per generare output (transformer custom, code generation).
- Language Service API — completamento, hover, rename, diagnostica: la base per editor e linter.
L’obiettivo esplicito è permettere ad Angular, Vue, Svelte e typescript-eslint di usare il compilatore nativo, e sostituire le integrazioni esistenti in VS Code.
Linguaggio e lib. Nuova opzione es2026 per target e lib; supporto a type negli import attribute; supporto ai source phase import, la proposta TC39 che permette di importare la “sorgente” di un modulo senza eseguirlo — il caso d’uso principale è WebAssembly:
import source wasmModule from "./image-filter.wasm";
// wasmModule è un WebAssembly.Module compilato ma non istanziato
const instance = await WebAssembly.instantiate(wasmModule, imports);
Arrivano anche le dichiarazioni per i nuovi metodi degli iterator e per Promise.allKeyed/Promise.allSettledKeyed, che fanno quello che Promise.all fa con gli array, ma su un oggetto con chiavi:
const { user, posts } = await Promise.allKeyed({
user: fetchUser(id),
posts: fetchPosts(id),
});
// niente più destructuring posizionale [user, posts] da tenere allineato
Performance. Oltre a quello già guadagnato con la 7.0, il piano elenca ottimizzazioni mirate: costruzione più veloce dei tipi union, fast path per il narrowing su assegnazione, uguaglianza e switch/case sulle union, rappresentazioni più compatte delle tabelle interne del checker, e soprattutto esperimenti su come distribuire i file tra i checker — cioè ridurre il lavoro duplicato di cui parlavamo sopra.
Infrastruttura. Una build WebAssembly (wasip1) del compilatore, pensata tra l’altro per far girare il Playground nel browser con il nuovo compilatore, e build per Android ARM64.
ℹ️ Nota
Il piano di iterazione è un piano, non una promessa: alcune voci sono marcate come “investigate” (ad esempio il supporto alle package map di Node 26) e potrebbero slittare. Al momento in cui scrivo la 7.1 non è ancora in beta.
Checklist di migrazione
- Installa
typescript@7in un branch ed eseguinpx tsc --noEmit: gli errori di configurazione compaiono subito, prima di quelli di tipo. - Sistema le opzioni rimosse usando la tabella sopra (
baseUrl,moduleResolution: node,target: es5). - Aggiungi esplicitamente
typesper i globali che usi (node, il test runner). - Imposta
rootDirse i sorgenti non sono nella root del progetto, e verifica la struttura dioutDir. - Aggiungi le dichiarazioni
declare moduleper gli import di asset senza binding. - Aggiorna gli snapshot che contengono messaggi d’errore o
.d.tsgenerati. - Se usi typescript-eslint o un framework con file non-
.ts, configura l’alias con@typescript/typescript6fino alla 7.1. - Fissa
--checkersnello script di type-check, così locale e CI si comportano allo stesso modo.
FAQ
❓ TypeScript 7 cambia il modo in cui scrivo i tipi?
No. La 7.0 è un porting del compilatore esistente: stessa sintassi, stessa inferenza, stesso
narrowing. Le differenze che incontri vengono dai nuovi default del tsconfig.json, dalle opzioni
rimosse e da pochi casi limite come il trattamento Unicode dei template literal.
❓ Perché con --checkers diversi potrei vedere errori diversi?
Ogni checker costruisce tipi in modo indipendente sulla propria porzione di file. Con lo stesso numero di checker la divisione dei file è sempre identica e i risultati deterministici; cambiando quel numero cambia la divisione, e in casi rari può emergere un comportamento che dipende dall’ordine. Usare lo stesso valore in locale e in CI elimina il problema.
❓ Quanti checker devo usare?
Il default (4) è un buon punto di partenza. Su macchine con molti core e codebase grandi, 6-8 possono ridurre ancora i tempi, al costo di più memoria. Su runner CI piccoli (2 core, poca RAM) conviene restare sul default o scendere.
❓ Posso ancora generare JavaScript ES5?
Non con tsc: target: es5 e downlevelIteration sono stati rimossi. Se devi supportare
ambienti molto vecchi, compila con TypeScript verso un target moderno e poi fai il down-leveling
con Babel o SWC.
❓ typescript-eslint funziona con TypeScript 7?
Non ancora direttamente, perché dipende dall’API programmatica che arriverà con la 7.1. Nel
frattempo si installa @typescript/typescript6 come alias del pacchetto typescript, così il
linter usa la 6.0 mentre il type-check in CI usa il compilatore nativo.
❓ Conviene aspettare la 7.1?
Dipende dallo stack. Per un progetto Node o React puro, la 7.0 è già utilizzabile e il guadagno di velocità è immediato. Se usi Vue, Svelte, Astro, MDX o template Angular, la 7.1 è la release che rende possibile la migrazione completa, editor incluso.
Conclusioni
TypeScript 7.0 è una release che si capisce meglio guardando cosa non cambia: il linguaggio. Tutto il resto — parallelismo, ordinamento stabile dei tipi, default moderni, opzioni legacy rimosse — serve a far girare lo stesso type checker molto più velocemente e a togliere di mezzo dieci anni di compatibilità verso ambienti che quasi nessuno usa più. La 7.1 chiude il cerchio restituendo l’API all’ecosistema.
Se non hai ancora letto la panoramica, parti dall’articolo su TypeScript 7 e 7.1.

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.