TypeScript 7, natywny kompilator i migracja
TypeScript dokłada do JavaScriptu statyczne typy sprawdzane przed uruchomieniem kodu. W lipcu 2026 doczekał się przepisania kompilatora z JavaScriptu na Go, co dało budowanie szybsze od ośmiu do dwunastu razy przy zachowaniu tego samego zachowania sprawdzania typów.
To najważniejsza zmiana w tym języku od lat i zarazem najbardziej niewidoczna, bo nie dodaje ani jednej nowej konstrukcji składniowej. Zmienia się wyłącznie czas, jaki spędzasz czekając.
Co dokładnie zmieniło się w wersji 7
Kompilator był dotąd napisany w TypeScripcie i uruchamiany na środowisku JavaScriptu, więc sam siebie kompilował. Nowa wersja to przepisanie tego kodu na Go, z kodem natywnym, współdzieloną pamięcią i wielowątkowością.
Skutek jest mierzalny i dotyczy tego, co boli codziennie. Pełne sprawdzenie typów w dużym projekcie skraca się z minut do kilkunastu sekund. Podpowiadanie w edytorze przestaje się zacinać na dużym pliku, bo usługa językowa działa na tym samym silniku.
Ważniejsze od samych liczb jest to, że reguły sprawdzania typów zostały utrzymane: siódemka sprawdza tak samo jak szóstka, więc to nie jest nowy język. Zmieniła się natomiast konfiguracja. strict jest teraz włączony domyślnie, module domyślnie ustawia się na esnext, types domyślnie jest puste zamiast wciągać wszystkie znalezione paczki deklaracji, a rootDir domyślnie wskazuje katalog projektu. Dawne ostrzeżenia stały się przy tym twardymi błędami: target: es5, baseUrl, moduleResolution: classic oraz node i node10, a także module: amd, umd i systemjs nie są już obsługiwane. Projekt idący z wersji piątej warto przeprowadzić najpierw przez szóstkę, bo to ona wprowadziła te wycofania.
Nie znaczy to, że migracja jest bezwarunkowa. Wersja 7.0 nie udostępnia żadnego interfejsu programistycznego, a nowy ma przyjść dopiero w 7.1. Narzędzia sięgające po kompilator z kodu, a nie z wiersza poleceń, pracują tymczasem na paczce zgodności @typescript/typescript6, która daje polecenie tsc6 i wystawia interfejs z szóstki obok binarki siódemki. Praktyczny skutek jest taki, że przepływy pracy z Vue, MDX, Astro i Svelte oraz sprawdzanie typów w szablonach Angulara nadal opierają się na wersji 6.0. Przed migracją sprawdź konkretnie te narzędzia, których używasz, bo to jest jedyny realny warunek blokujący.
Kiedy TypeScript naprawdę się opłaca
Warto to rozstrzygnąć uczciwie, bo odpowiedź nie brzmi „zawsze".
Opłaca się przy kodzie, który ktoś będzie czytał za pół roku, i przy zespole większym niż jedna osoba. Typy pełnią wtedy rolę dokumentacji, która nie może się zdezaktualizować, bo kompilator ją sprawdza.
Opłaca się przy refaktoryzacji. Zmiana nazwy pola w obiekcie używanym w czterdziestu miejscach jest w typowanym kodzie operacją mechaniczną, a w nietypowanym polowaniem na to, co się zepsuło.
Nie opłaca się w skrypcie na jedno użycie ani w prototypie, który ma sprawdzić hipotezę i pójść do kosza. Konfiguracja, deklaracje typów zależności i walka z kompilatorem kosztują wtedy więcej, niż dają.
Trzeci przypadek jest pośredni: mały projekt, który może urosnąć. Tu sensowną drogą bywa zwykły JavaScript z typami opisanymi w komentarzach dokumentacyjnych, sprawdzanymi przez kompilator bez kroku budowania. Dostajesz sporą część korzyści bez zmiany procesu.
Rzeczy, które warto ustawić na starcie
Konfiguracja domyślna jest łagodna, a łagodność w tym języku oznacza mniej wykrytych błędów.
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"verbatimModuleSyntax": true
}
}Pierwsza opcja jest oczywista i od siódemki działa domyślnie, więc wpis w pliku tylko utrwala ten stan. Druga jest najbardziej wartościowa z pomijanych: sprawia, że odczyt elementu tablicy po indeksie daje typ z możliwością braku wartości, co odpowiada rzeczywistości.
const dni = ['pon', 'wt', 'sr']
const dzien = dni[5]
console.log(dzien.toUpperCase())Bez tej opcji kompilator uznaje trzecią linię za poprawną i program wywala się dopiero w czasie działania. Z nią zgłasza błąd w miejscu, w którym problem faktycznie jest, i zmusza do napisania, co zrobić, gdy elementu nie ma.
Trzecia rozróżnia pole nieobecne od pola ustawionego na wartość pustą, co ma znaczenie przy obiektach wysyłanych do interfejsów zewnętrznych. Czwarta wymusza jawne oznaczanie importów typów.
import type { Uzytkownik } from './typy'
import { zapiszUzytkownika } from './api'To rozróżnienie nie jest kosmetyczne. Import typu znika przy budowaniu bez śladu, a import bez tego słowa zostaje i wciąga cały moduł razem z jego skutkami ubocznymi, nawet jeśli używasz z niego wyłącznie definicji typu.
Osobna rada dotyczy projektu, który dopiero przechodzi na typy: włączaj te opcje po jednej, a nie wszystkie naraz. Każda generuje własną klasę błędów i mieszanie ich w jednym podejściu daje listę kilkuset pozycji, której nikt nie przejrzy.
Typy, które robią różnicę
Poza podstawami są trzy mechanizmy odpowiadające za większość realnej wartości.
Typy zawężane przez sprawdzenie pozwalają opisać stan, w którym pewne pola istnieją tylko w pewnych sytuacjach.
type Odpowiedz =
| { status: 'sukces'; dane: Uzytkownik }
| { status: 'blad'; komunikat: string }
function obsluz(o: Odpowiedz) {
if (o.status === 'sukces') {
console.log(o.dane.nazwa)
} else {
console.log(o.komunikat)
}
}To usuwa całą klasę błędów, w której ktoś sięga po dane przy odpowiedzi z błędem. Kompilator po prostu nie pozwala.
Drugi mechanizm to wnioskowanie zamiast deklarowania. Typ wyprowadzony z rzeczywistej wartości nie rozjedzie się z nią, a zapisany ręcznie potrafi. Dlatego zamiast opisywać kształt odpowiedzi z serwera osobnym interfejsem, lepiej wyprowadzić go ze schematu walidacji, na przykład w Zodzie, i mieć jedno źródło prawdy.
Trzeci to typy narzędziowe, które przekształcają istniejące typy zamiast tworzyć nowe.
type Uzytkownik = {
id: string
email: string
hasloHash: string
utworzono: Date
}
type UzytkownikPubliczny = Omit<Uzytkownik, 'hasloHash'>
type AktualizacjaUzytkownika = Partial<Pick<Uzytkownik, 'email'>>
type WynikPobrania = Awaited<ReturnType<typeof pobierzUzytkownika>>Wybranie kilku pól, uczynienie wszystkich opcjonalnymi albo wyciągnięcie typu zwracanego przez funkcję to operacje, które oszczędzają utrzymanie, bo zmiana źródła przenosi się automatycznie. Pierwszy z tych typów ma tu dodatkową wartość: dopisanie nowego pola wrażliwego do modelu nie sprawi, że wycieknie ono przez interfejs, o ile publiczny kształt powstaje przez odejmowanie, a nie przez wyliczanie pól od zera.
Warto natomiast znać granicę. Typy zaawansowane potrafią wyrazić bardzo dużo i to jest kuszące, ale typ, którego komunikat błędu zajmuje trzydzieści linii, kosztuje zespół więcej, niż daje. Prostszy typ plus komentarz bywa lepszym rozwiązaniem niż popisowa konstrukcja.
Jak przenieść istniejący projekt
Migracja projektu napisanego w JavaScripcie to najczęstsza sytuacja i zarazem ta, w której najłatwiej zniechęcić zespół.
Zacznij od włączenia sprawdzania na kodzie, który już masz, bez zmiany rozszerzeń plików. Kompilator potrafi analizować pliki JavaScriptu i zgłaszać błędy, których nie da się przeoczyć: literówki w nazwach właściwości, wywołania z niewłaściwą liczbą argumentów, odwołania do rzeczy nieistniejących. To zwykle pierwsza garść realnych usterek i dobry argument w rozmowie o sensowności całego przedsięwzięcia.
Drugi krok to kolejność przenoszenia. Zaczynaj od plików bez zależności wewnętrznych, czyli od funkcji pomocniczych i stałych, potem idź w górę. Odwrotna kolejność oznacza, że każdy przeniesiony plik ciągnie za sobą brakujące typy z całej reszty projektu.
Trzeci to pilnowanie, żeby nowy kod powstawał już w wersji typowanej. Bez tej zasady migracja nie ma końca, bo tempo dopisywania nietypowanych plików bywa wyższe niż tempo przenoszenia starych.
Czwarty dotyczy plików, których nie da się przenieść od razu. Zamiast blokować całość, wyłącz sprawdzanie w konkretnym pliku komentarzem i dopisz go do listy do zrobienia. Wyłączenie punktowe z notatką jest uczciwsze niż globalne rozluźnienie konfiguracji, bo widać, ile zostało.
Warto na koniec ustalić miarę postępu. Liczba plików przeniesionych do liczby wszystkich, wypisywana raz w tygodniu, robi więcej dla zakończenia migracji niż jakakolwiek deklaracja, bo pokazuje, czy praca w ogóle posuwa się do przodu.
Wydajność sprawdzania w dużym projekcie
Nawet po przepisaniu kompilatora duży projekt potrafi sprawdzać się wolniej, niż wynikałoby z jego rozmiaru, i przyczyna zwykle leży w kodzie, nie w narzędziu.
Pierwszy podejrzany to typy warunkowe i rekurencyjne o dużej złożoności. Kompilator musi je rozwinąć przy każdym użyciu, a zagnieżdżone konstrukcje potrafią mnożyć pracę wykładniczo. Jeden zbyt sprytny typ narzędziowy używany w stu miejscach potrafi zdominować czas sprawdzania całego projektu.
Drugi to brak podziału na projekty składowe. Duże repozytorium sprawdzane jako jedna całość przelicza wszystko przy każdej zmianie, podczas gdy podział na osobne jednostki z zapisem stanu pozwala pominąć to, co się nie zmieniło.
Trzeci to import wciągający więcej, niż potrzeba. Sięgnięcie po jeden typ z pakietu, który eksportuje ich tysiąc, zmusza kompilator do przeczytania całości deklaracji.
Diagnostyka jest prosta: wbudowane liczniki pokazują, ile czasu poszło na parsowanie, a ile na sprawdzanie typów, oraz który plik zajął najwięcej. Zanim zaczniesz zgadywać, uruchom pomiar, bo intuicja w tej sprawie myli się częściej, niż trafia.
TypeScript kontra alternatywy
| Rozwiązanie | Sprawdzanie | Krok budowania | Kiedy wybrać |
|---|---|---|---|
| TypeScript | Przed uruchomieniem, pełne | Tak | Projekt utrzymywany dłużej niż kwartał |
| JavaScript z typami w komentarzach | Przed uruchomieniem, węższe | Nie | Mały projekt, brak chęci na konfigurację |
| Czysty JavaScript | Brak | Nie | Skrypt jednorazowy, prototyp |
| Walidacja w czasie działania | Podczas wykonania | Zależnie | Dane z zewnątrz, których typy nie obejmują |
Ostatni wiersz to nie alternatywa, tylko uzupełnienie, i mylenie tych dwóch rzeczy jest najczęstszym nieporozumieniem wokół tego języka. Typy znikają przy kompilacji, więc odpowiedź z serwera opisana jako obiekt o określonym kształcie może w rzeczywistości być czymkolwiek. Kompilator w to uwierzy, a program wywali się dopiero przy odwołaniu do brakującego pola.
Właściwy układ jest taki: walidacja na granicy systemu, typy wewnątrz. Wszystko, co przychodzi z sieci, z formularza, z pliku albo ze zmiennych środowiskowych, przechodzi przez sprawdzenie w czasie działania, a dalej pracujesz na typach wyprowadzonych z tego sprawdzenia.
Codzienna praca i ekosystem
W Reakcie typy dotyczą głównie właściwości komponentów i stanu, i to jest miejsce, gdzie zwrot z inwestycji widać najszybciej, bo błędne przekazanie właściwości jest najczęstszą pomyłką przy zmianach.
Po stronie serwera wartość rośnie przy wspólnym kodzie. tRPC wyprowadza typy klienta wprost z kodu serwera, więc zmiana sygnatury procedury zapala błąd w komponencie, a nie u użytkownika. Podobnie działa wnioskowanie w bibliotekach zapytań, gdzie kształt danych bierze się z funkcji pobierającej.
Jeśli system typów wciąż wydaje Ci się zbiorem reguł do zapamiętania, zamiast czytać kolejny opis, warto przejść interaktywne lekcje Visual Types. Generyki, typy warunkowe i infer znacznie łatwiej zrozumieć, obserwując, jak zmienia się wynik przy zmianie wejścia, niż z samego opisu składni.
Warto też zadbać o typy zależności. Biblioteki publikują je dziś zwykle razem z kodem, a te, które tego nie robią, mają najczęściej osobne paczki z deklaracjami. Zależność bez typów wprowadza do projektu wyspę, w której kompilator nie sprawdza niczego, i ta wyspa ma tendencję do rozrastania się.
Ostatnia rzecz to kompilacja a sprawdzanie. Większość narzędzi budujących usuwa typy bez ich sprawdzania, bo tak jest szybciej. Znaczy to, że projekt potrafi zbudować się mimo błędów typów, jeśli nikt nie uruchamia sprawdzania osobno. Dopisz je jako oddzielny krok w potoku, inaczej cała ta praca jest tylko podpowiadaniem w edytorze. Tak samo zachowują się środowiska wykonujące pliki .ts wprost, na przykład Bun: typy są tam usuwane bez etapu kompilacji, więc szybkie uruchomienie kodu nie mówi nic o jego poprawności.
Typowe błędy
Pierwszy to używanie typu dowolnego jako rozwiązania problemu. Wyłącza on sprawdzanie w miejscu, w którym go użyto, i rozlewa się dalej przez przypisania. Gdy naprawdę nie znasz typu, użyj typu nieznanego, który wymusza sprawdzenie przed użyciem.
Drugi to rzutowanie zamiast walidacji. Zapis mówiący kompilatorowi, że odpowiedź z serwera ma określony kształt, jest deklaracją bez pokrycia i przenosi błąd z kompilacji na czas działania.
Trzeci to brak sprawdzania typów w potoku budowania. Narzędzie usuwające typy bez sprawdzania przepuszcza błędy, a zespół dowiaduje się o nich z produkcji.
Czwarty to zbyt luźna konfiguracja. Bez trybu ścisłego znaczna część błędów, dla których się ten język wybiera, po prostu nie jest wykrywana.
Piąty to opisywanie typów, które kompilator sam wyprowadzi. Ręczna adnotacja przy zmiennej inicjalizowanej wartością nie dodaje bezpieczeństwa, a dokłada miejsce do zaktualizowania przy każdej zmianie.
Szósty to budowanie skomplikowanych typów tam, gdzie wystarczy prosty. Komunikat błędu z takiego typu bywa nieczytelny, a osoba, która trafi na niego za rok, straci na tym więcej czasu, niż zaoszczędził autor.
FAQ
Czym różni się TypeScript 7 od poprzednich wersji?
Przepisaniem kompilatora z JavaScriptu na Go, co daje budowanie od ośmiu do dwunastu razy szybsze. Składnia i reguły sprawdzania typów pozostały zgodne z wersją 6.0, więc sam kod kompiluje się tak samo, natomiast domyślne ustawienia w tsconfig.json się zmieniły.
Czy migracja na wersję 7 jest ryzykowna?
Sprawdzanie typów zachowuje zgodność z wersją 6.0, natomiast domyślna konfiguracja się zmieniła, a część dawnych opcji, jak target: es5 czy baseUrl, przestała być obsługiwana. Drugie ryzyko to narzędzia sięgające po kompilator z kodu: 7.0 nie ma jeszcze interfejsu programistycznego, więc Vue, Astro, Svelte, MDX i szablony Angulara stoją na paczce zgodności z szóstką.
Czy typy chronią przed błędnymi danymi z serwera?
Nie. Typy znikają przy kompilacji, więc odpowiedź opisana jako obiekt o określonym kształcie może być czymkolwiek. Na granicy systemu potrzebujesz walidacji w czasie działania, a typy wyprowadzaj z niej.
Czy warto używać TypeScriptu w małym projekcie?
Jeśli projekt ma żyć dłużej niż kilka tygodni i dotykać go będzie więcej niż jedna osoba, tak. Przy skrypcie jednorazowym lub prototypie do wyrzucenia konfiguracja kosztuje więcej, niż daje, a pośrednim rozwiązaniem są typy opisane w komentarzach dokumentacyjnych.
Od czego zacząć przechodzenie istniejącego projektu?
Od włączenia sprawdzania na istniejącym kodzie JavaScriptu, bez zmiany rozszerzeń plików. Potem przenoś plik po pliku, zaczynając od tych bez zależności, i włączaj opcje ścisłe pojedynczo, żeby liczba błędów w jednym podejściu pozostała możliwa do przejrzenia.
Wydanie siódmej wersji opisuje ogłoszenie zespołu, a pełną dokumentację znajdziesz na stronie języka.