tsconfig.json to plik konfiguracyjny projektu TypeScript. Gdy uruchamiasz tsc bez argumentów, kompilator szuka go w bieżącym folderze (a potem w folderach nadrzędnych), odczytuje opcje z compilerOptions i sprawdza pliki wskazane przez include. Mały, ale kompletny przykład:
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"strict": true,
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
To kompiluje każdy plik TypeScript z src do JavaScriptu w dist jako kod ES2022, z regułami modułów Node.js i wszystkimi sprawdzeniami strict. npx tsc --init generuje dłuższy plik startowy z komentarzem przy każdej opcji.
Plik to JSON z komentarzami: akceptowane są komentarze //, komentarze /* */ i przecinki na końcu.
Zalecana konfiguracja startowa
Dla aplikacji albo skryptu w Node.js:
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"target": "es2024",
"lib": ["es2024"],
"module": "nodenext",
"types": ["node"],
"strict": true,
"noUncheckedIndexedAccess": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"sourceMap": true
},
"include": ["src"]
}
Do typów Node.js potrzebne jest npm install --save-dev @types/node. Przy module: "nodenext" pole "type" w package.json decyduje, czy plik .ts jest modułem ES ("type": "module"), czy CommonJS ("type": "commonjs", które zapisuje npm init -y). W nowych projektach, które używają import i export, ustaw "type": "module".
Dla kodu budowanego przez bundler, taki jak Vite, esbuild albo webpack, JavaScript zapisuje bundler, a tsc tylko sprawdza:
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022", "dom"],
"module": "preserve",
"noEmit": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
Startery frameworków (Vite, Next.js, Angular) generują własny tsconfig.json. Zacznij od ich wersji, zamiast ją zastępować.
Opcje, które mają znaczenie
| Opcja | Co robi | Domyślnie w TypeScript 7 |
|---|---|---|
target | Wersja JavaScriptu na wyjściu, nowsza składnia jest przepisywana dla starszych targetów | es2025 |
lib | Typy wbudowanych API znane modułowi sprawdzania (Array.prototype.at, Map, document) | Zgodne z target, plus DOM |
module | Rodzaj emitowanego kodu modułów i stosowane reguły modułów | esnext |
moduleResolution | Jak ścieżki importów są znajdowane na dysku | nodenext albo node16 przy pasującym module, w przeciwnym razie bundler |
strict | Włącza całą rodzinę sprawdzeń strict | true |
rootDir | Folder, którego struktura jest odwzorowywana w outDir | Folder, w którym leży tsconfig.json |
outDir | Gdzie trafiają pliki .js (i .d.ts) | Obok każdego pliku źródłowego |
include / exclude / files | Które pliki należą do projektu | Każdy plik .ts w folderze |
types | Które pakiety @types ładują się bez importu | [], żadne |
noEmit | Tylko sprawdzanie, nic nie jest zapisywane | false |
declaration | Zapisuje też pliki deklaracji typów .d.ts | false |
sourceMap | Zapisuje pliki .js.map dla debuggerów | false |
esModuleInterop | Pozwala, by import x from "cjs-package" działało z pakietami CommonJS | true (nie da się wyłączyć) |
skipLibCheck | Pomija sprawdzanie typów w plikach .d.ts, także w node_modules | false |
target i lib
target mówi, na jakiej wersji JavaScriptu ma działać wyjście. Składnia nowsza niż target jest przepisywana: przy "target": "es2017" pole klasy albo ??= zamienia się w starszy kod. Najniższy target, jaki akceptuje TypeScript 7, to es2015 (es6), a es5 usunięto.
target nie dodaje brakujących API w czasie wykonania. Opisanie ich to zadanie lib, a dostarczenie to zadanie polyfilla. lib mówi modułowi sprawdzania, które wbudowane obiekty i metody istnieją. Jego wartość domyślna wynika z target i obejmuje też typy DOM przeglądarki, więc document przechodzi sprawdzanie typów nawet w projekcie Node.js, dopóki sam nie ustawisz lib. Użycie metody z nowszego standardu niż twoje lib to błąd kompilacji:
src/b.ts(1,24): error TS2550: Property 'toSorted' does not exist on type 'number[]'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2023' or later.
Uruchamialne przykłady w tej dokumentacji używają lib es2022, więc toSorted, Object.groupBy i nowe metody Set nie są w nich dostępne.
module i moduleResolution
module ma trzy sensowne wartości dla nowego kodu:
nodenext: dla kodu uruchamianego przez Node.js. Każdy plik jest ESM albo CommonJS zależnie od rozszerzenia (.mts,.cts) albo pola"type"w najbliższympackage.json, dokładnie tak, jak decyduje Node. Względne importy w modułach ES wymagają rozszerzenia pliku, zapisanego jako.js, mimo że źródło to.ts:import { add } from "./math.js".moduleResolutiondopasowuje się automatycznie.preserve: dla kodu przetwarzanego przez bundler. Importy zostają tak, jak je zapisano, a rozwiązywanie korzysta z regułbundler, które pozwalają na importy bez rozszerzenia.esnext: zwykłe wyjście w modułach ES z rozwiązywaniembundler. To wartość domyślna, gdymodulenie jest ustawione.
Częsty pierwszy błąd przy konfiguracji z tsc --init bierze się stąd, że npm init -y zapisuje "type": "commonjs":
src/main.ts(1,10): error TS1295: ECMAScript imports and exports cannot be written in a CommonJS file under 'verbatimModuleSyntax'.
Plik używa import, ale package.json mówi, że projekt jest w CommonJS. Zmień "type" na "module". Importy i eksporty szczegółowo omawia strona o modułach.
strict i opcje sprawdzania
"strict": true włącza naraz grupę sprawdzeń: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, strictBuiltinIteratorReturn, noImplicitThis i useUnknownInCatchVariables. To ustawienie domyślne w TypeScript 7, włączone w każdym uruchamialnym przykładzie tutaj. Kod pisany pod tryb strict zawęża wartość, która może nie istnieć, zanim jej użyje:
Bez adnotacji typu parametru nie da się wywnioskować, a noImplicitAny to zgłasza:
index.ts(2,17): error TS7006: Parameter 'n' implicitly has an 'any' type.
strict nie obejmuje każdego przydatnego sprawdzenia. Warto dodać dwa: noUncheckedIndexedAccess (element tablicy albo odczyt przez index signature ma typ T | undefined) i exactOptionalPropertyTypes (właściwości opcjonalnej nie da się jawnie ustawić na undefined). tsc --init włącza oba. Sprawdzenia stylu, takie jak noUnusedLocals, noImplicitReturns i noFallthroughCasesInSwitch, są domyślnie wyłączone.
Które pliki: include, exclude, rootDir, outDir
Bez include albo files projekt zawiera każdy plik .ts, .tsx i .d.ts z folderu i jego podfolderów. node_modules jest zawsze pomijany. outDir też jest pomijany, ale tylko dopóki nie ustawisz exclude: własna lista exclude zastępuje to ustawienie domyślne, więc dopisz do niej folder wyjściowy. include i exclude przyjmują wzorce glob:
{
"include": ["src", "scripts/**/*.ts"],
"exclude": ["src/**/*.test.ts"]
}
exclude tylko filtruje to, co znalazło include. Plik importowany przez inny włączony plik i tak jest kompilowany.
outDir to miejsce na wyjście, a rootDir to część drzewa źródeł, która jest w nim odwzorowywana: przy "rootDir": "./src" plik src/api/users.ts staje się dist/api/users.js. Ustaw oba. rootDir domyślnie wskazuje folder, w którym leży tsconfig.json, więc jeśli ustawisz tylko outDir, a źródła leżą w src, TypeScript 7 zatrzyma się z komunikatem:
error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.
Opcje wyjścia: noEmit, declaration, sourceMap
"noEmit": truesprawia, żetsctylko sprawdza typy. Używaj tego, gdy JavaScript tworzy inne narzędzie (bundler,tsx, usuwanie typów w Node.js)."declaration": truezapisuje plik.d.tsdla każdego modułu, czyli typy bez kodu. Biblioteki go potrzebują, żeby ich użytkownicy dostali typy.declarationMapdodaje mapy, dzięki którym "go to definition" prowadzi do źródła.ts."sourceMap": truezapisuje pliki.js.map, żeby debuggery i stack trace wskazywały linie w.ts."noEmitOnError": truenie zapisuje niczego, dopóki są błędy typów. Bez tegotsczgłasza błędy i mimo to zapisuje JavaScript.
types, esModuleInterop i skipLibCheck
types wymienia pakiety @types, które ładują się globalnie, bez importu. W TypeScript 7 domyślnie jest to pusta lista, więc po npm install --save-dev @types/node dopisujesz też "types": ["node"]. Inaczej process i require pozostają nieznane (error TS2591: Cannot find name 'process'). Test runnery z funkcjami globalnymi, takimi jak describe z Jest, trafiają na tę samą listę.
esModuleInterop sprawia, że działają domyślne importy pakietów CommonJS (import express from "express"). W TypeScript 7 jest zawsze włączone, a ustawienie go na false to błąd.
skipLibCheck pomija sprawdzanie typów w plikach deklaracji. Przyspiesza budowanie i pozwala uniknąć błędów w node_modules, których i tak nie naprawisz, kosztem tego, że nie zauważysz konfliktów między typami dwóch bibliotek. Większość projektów to włącza.
Współdzielenie ustawień przez extends
extends wczytuje inną konfigurację i pozwala temu plikowi nadpisać jej części:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
compilerOptions są łączone opcja po opcji, a include, exclude i files z bazowego pliku są zastępowane, a nie łączone, jeśli ten plik je ustawia. Ścieżki w pliku bazowym są rozwiązywane względem pliku bazowego. extends przyjmuje też pakiet: projekt @tsconfig/bases publikuje po jednym na każde środowisko, na przykład npm install --save-dev @tsconfig/node24, a potem "extends": "@tsconfig/node24/tsconfig.json".
Żeby zobaczyć ostateczne ustawienia po zastosowaniu wszystkich extends i wartości domyślnych, uruchom:
npx tsc --showConfig
Opcje usunięte w TypeScript 7
TypeScript 6 oznaczył te ustawienia jako przestarzałe, a TypeScript 7 je usunął. Konfiguracja, która nadal używa któregoś z nich, kończy się błędem error TS5108 albo TS5102, na przykład Option 'baseUrl' has been removed. Please remove it from your configuration.
| Usunięte ustawienie | Użyj zamiast tego |
|---|---|
"target": "es5" | es2015 albo nowszy |
"moduleResolution": "node" (node10) albo "classic" | nodenext albo bundler |
"module": "amd", "umd", "system", "none" | nodenext, esnext albo preserve, a dla innych formatów bundler |
baseUrl | Wpisy paths względne wobec pliku tsconfig |
outFile | Bundler |
downlevelIteration | Nic: targety od es2015 wzwyż obsługują iterację natywnie |
"esModuleInterop": false, "allowSyntheticDefaultImports": false, "alwaysStrict": false | Usuń tę linię, te opcje są zawsze włączone |
Strona o TypeScript 7 wymienia pozostałe zmiany w tym wydaniu, w tym nowe wartości domyślne, których nie obejmuje ta tabela.
Najczęściej zadawane pytania
Czym jest tsconfig.json?
To plik konfiguracyjny projektu TypeScript. Jego obecność oznacza folder jako katalog główny projektu, compilerOptions ustawia, jak kompilator sprawdza i emituje kod, a include, exclude albo files mówią, które pliki należą do projektu. Uruchomienie tsc bez argumentów go odczytuje.
Jak utworzyć plik tsconfig.json?
Uruchom npx tsc --init w folderze projektu (z zainstalowanym TypeScriptem). Polecenie zapisuje tsconfig.json z zalecanymi ustawieniami i komentarzem przy każdej opcji. Możesz też napisać plik ręcznie: {} to poprawny tsconfig, który używa wszystkich wartości domyślnych.
Na co ustawić target w tsconfig?
Na najstarszą wersję JavaScriptu, na której ma działać twój kod. Dla aktualnego Node.js bezpieczne jest es2022 albo nowsze, a domyślna wartość w TypeScript 7 to es2025. target decyduje, która nowsza składnia jest przepisywana dla starszych silników, i wybiera też domyślne lib, czyli zestaw wbudowanych API znanych modułowi sprawdzania typów.
Czym różni się module od moduleResolution?
module decyduje, jaki rodzaj kodu modułów zapisuje kompilator (ES import/export albo CommonJS require) i które reguły modułów obowiązują. moduleResolution decyduje, jak na dysku znajdowana jest ścieżka importu, taka jak "./utils.js" albo "lodash". Używaj nodenext dla kodu uruchamianego przez Node.js, a module: "preserve" (co oznacza rozwiązywanie bundler) dla kodu przetwarzanego przez bundler.
Czy tsconfig.json może mieć komentarze?
Tak. Kompilator czyta go jako JSON z komentarzami: dozwolone są komentarze // i /* */ oraz przecinki na końcu, dlatego tsc --init zapisuje plik pełen zakomentowanych opcji. Inne narzędzia, które parsują go jako ścisły JSON, mogą się na nich wywrócić.