TypeScript: A Detailed Explanation of TypeScript's…
Last updated: 2026-08-26
tsconfig.json is the configuration hub for TypeScript projects—it tells the compiler how to compile the code, check types, and output results. Understanding these configuration options is essential for ensuring your TypeScript project runs smoothly.
1. tsconfig.json Basics
(1) Create a configuration file
# Automatically Generate Default Configuration
tsc --init
# Generate a detailed configuration with comments
tsc --init --typescript
(2) Basic Structure
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
(3) Top-level fields
| Field | Description |
|---|---|
compilerOptions |
Compiler Options |
include |
Included files (glob mode) |
exclude |
Excluded files |
files |
List of explicitly specified files |
references |
Project References (monorepo) |
extends |
Inherit from another configuration file |
2. Core Compilation Options
(1) target—compilation target
Specify the version of the compiled JavaScript:
{
"compilerOptions": {
"target": "ES2020"
}
}
| Value | Description | Recommended Scenarios |
|---|---|---|
"ES5" |
Compatible with older browsers | Supports IE11 |
"ES2018" |
Modern browsers | General Web projects |
"ES2020" |
Latest Stable Features | Recommended |
"ESNext" |
Latest Proposal Features | Cutting-Edge Projects |
target option only affects syntax transformation (e.g., arrow functions → regular functions); it does not affect type checking. Type checking is controlled by the lib option.
(2) module—Module System
{
"compilerOptions": {
"module": "ESNext"
}
}
| Value | Description | Recommended Scenarios |
|---|---|---|
"CommonJS" |
Node.js Default | Node Project |
"ESNext" / "ES2015" |
ES Modules | Browser/Deno/Vite |
"UMD" |
General-Purpose Modules | Library Releases |
"System" |
SystemJS | Legacy Module Loader |
(3) moduleResolution—Module Resolution Strategy
{
"compilerOptions": {
"moduleResolution": "node"
}
}
| Value | Description |
|---|---|
"node" |
Node.js style (recommended) |
"classic" |
Old TS Style (Not Recommended) |
"bundler" |
Vite/esbuild and Other Build Tools (TS 5.0+) |
(4) lib—Type Library
Specify the available built-in type declarations:
{
"compilerOptions": {
"lib": ["ES2020", "DOM", "DOM.Iterable"]
}
}
| Value | Type provided |
|---|---|
"ES2020" |
Promise, Array.flat, BigInt, etc. |
"DOM" |
document, window, HTMLElement, etc. |
"DOM.Iterable" |
NodeList 's for...of |
"ES2020.String" |
ES2020 Methods for Strings |
"ES2020.Promise" |
Promise.allSettled, etc. |
target automatically includes the corresponding lib. If you explicitly specify a lib, it will no longer be automatically included—you’ll need to manually list all required libraries. Web projects typically require ["ES2020", "DOM"].
(5) outDir and rootDir
{
"compilerOptions": {
"outDir": "./dist", // Compilation Output Directory
"rootDir": "./src", // Source Code Root Directory(Preserve the directory structure)
"declaration": true, // Generate .d.ts Statement
"sourceMap": true // Generate .js.map Source Code Mapping
}
}
3. Strict Mode Option
(1) Strict mode fully enabled
{
"compilerOptions": {
"strict": true
}
}
strict: true is equivalent to enabling all of the following options at the same time:
| Option | Description |
|---|---|
strictNullChecks |
null/undefined cannot be assigned to other types |
strictFunctionTypes |
Inverse Check of Function Parameters |
strictBindCallApply |
Strict checking of bind/call/apply |
strictPropertyInitialization |
Class properties must be initialized |
noImplicitAny |
Implicit any prohibited |
noImplicitThis |
Disallow implicit this as any |
alwaysStrict |
Emit "use strict" |
(2) Understanding Each Point
// strictNullChecks: true
let name: string = null; // ❌ null Cannot be assigned string
let name2: string | null = null; // ✅ Explicit Declaration
// noImplicitAny: true
function greet(name) { // ❌ The parameter is implicitly set to any
return name;
}
function greet2(name: string) { // ✅ Explicit Annotation
return name;
}
// strictPropertyInitialization: true
class User {
name: string; // ❌ Property not initialized
age: number = 0; // ✅ Has an initial value
}
strict for all new projects. It is the cornerstone of TypeScript's type safety—although it may require a bit more effort to add type annotations initially, it helps prevent a large number of runtime errors.
▶ Example: Comparison Before and After Strict Mode
// strict: false — the following code does not generate any errors, but it may crash during execution.
let name: string = null as any; // Runtime name.toUpperCase() Breakdown
function greet(user) { // user Implicit any
return user.name; // No type checking
}
// strict: true — captured at compile time
let name2: string | null = null; // ✅ Must be explicitly declared null
function greet2(user: { name: string }) { // ✅ Parameters must be labeled
return user.name;
}
Output:
Compile error — strict mode catches null and implicit any issues
4. Module and Path Configuration
(1) Path Mapping
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@utils/*": ["src/utils/*"],
"@components/*": ["src/components/*"]
}
}
}
(2) resolveJsonModule
{
"compilerOptions": {
"resolveJsonModule": true,
"esModuleInterop": true
}
}
// Import Allowed JSON Documents
import config from "./config.json";
console.log(config.port); // ✅
(3) allowJs with checkJs
{
"compilerOptions": {
"allowJs": true, // Allow compilation JS Documents
"checkJs": false // Do not check JS File Type(Compile Only)
}
}
allowJs to allow TS and JS to coexist, then gradually add types.
5. Code Quality Options
{
"compilerOptions": {
"noUnusedLocals": true, // Error: Unused local variables
"noUnusedParameters": true, // Error: Unused function parameters
"noImplicitReturns": true, // Error: Function branch does not return a value
"noFallthroughCasesInSwitch": true, // Error: switch fallthrough
"forceConsistentCasingInFileNames": true // File names must be case-sensitive
}
}
(1) Demo
// noUnusedLocals: true
let unused = 42; // ❌ Unused variables
// noUnusedParameters: true
function handler(event: Event) { // ❌ event Unused
console.log("Trigger");
}
// Fix: Use _ prefix tag
function handler2(_event: Event) { // ✅ _ The prefix does not cause an error
console.log("Trigger");
}
// noImplicitReturns: true
function getGrade(score: number): string {
if (score >= 90) return "A";
if (score >= 80) return "B";
// ❌ Missing else Branched return
}
// noFallthroughCasesInSwitch: true
switch (action) {
case "create":
createItem();
// ❌ Missing break——case Penetration
case "update":
updateItem();
break;
}
6. Common Configuration Templates
(1) Node.js Backend Project
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
(2) React Front-End Project (Vite)
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
(3) Library Projects (Publishing npm Packages)
{
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"moduleResolution": "node",
"declaration": true,
"declarationDir": "./dist/types",
"outDir": "./dist/esm",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
▶ Example: Target and Module Configuration Effects
// The same source code compiles differently based on target/module settings
async function fetchData(): Promise<string> {
let response = await Promise.resolve("hello");
return response.toUpperCase();
}
export function greet(name: string): string {
return `Hello, ${name}!`;
}
Output:
Compilation output varies by target/module — see explanations below
With "target": "ES5", async/await compiles to a verbose state machine;
with "target": "ES2020", it stays as native async/await.
With "module": "CommonJS", exports become exports.greet = greet;
with "module": "ESNext", they remain as export function greet.
▶ Example: Strict Null Checks in Practice
// strictNullChecks: true prevents common null bugs
interface User { name: string; email: string | null; }
function getDisplayName(user: User): string {
// return user.email.toLowerCase(); // ❌ Object is possibly null
return user.email ? user.email.toLowerCase() : user.name; // ✅
}
function findUser(id: number): User | null {
return id > 0 ? { name: "Alice", email: "a@b.com" } : null;
}
let user = findUser(1);
// console.log(user.name); // ❌ user is possibly null
if (user) {
console.log(user.name); // ✅ narrowed to User
}
Output:
Alice
❓ FAQ
strict make code harder to write?strict from the start—once you get used to it, you’ll actually find it awkward not to use it. If your project is already large, you can enable it gradually: first enable noImplicitAny, then strictNullChecks, and finally enable strict fully.noEmit: true allows TypeScript to perform only type checking without outputting JS files. The Vite project uses Vite (esbuild) for compilation and bundling, while tsc is responsible only for type checking—so there’s no need for tsc to output files. Use tsc --noEmit for type checking in CI, and let Vite handle real-time compilation during development..d.ts files, which can significantly speed up compilation. The downside is that it may miss errors in third-party type declarations—but the risk is extremely low (the @types package is community-reviewed). Enabling skipLibCheck in large projects can save 30% or more in compilation time.extends for configuration inheritance?tsconfig files (e.g., frontend, Node scripts, and tests), use extends to share the base configuration and override only the differences. tsconfig.node.json can extends tsconfig.json, modifying only options such as target and module. This avoids configuration duplication.📖 Summary
tsconfig.jsonis the configuration hub for TypeScript projects—it contains three core fields:compilerOptions,include, andexcludetargetcontrols the JavaScript version,modulecontrols the module system, andlibcontrols the available type librariesstrict: trueEnable all strict checks—must be enabled for new projects- Path mapping (baseUrl + paths) uses aliases to replace long absolute paths
- Code quality options (such as noUnusedLocals and noImplicitReturns) have been further enhanced
- Different project types have different recommended configuration templates—Node backend, React frontend, and library projects
📝 Exercises
- Basic Exercise (Difficulty ⭐): Use
tsc --initto generate the defaulttsconfig.jsonfile, change thetargetto ES2020, enablestrict, and setoutDirtodist. Create a simple TypeScript file, compile it, and verify that it works. - Advanced Exercise (Difficulty ⭐⭐): Configure a
tsconfig.jsonfile that supports the path alias@/*→src/*. Createsrc/utils/math.tsandsrc/main.ts, and use the alias to import functions frommath.tsintomain.ts. - Challenge (Difficulty: ⭐⭐⭐): Design a configuration hierarchy for a monorepo project—base
tsconfig.base.json(shared options),packages/app/tsconfig.json(extends base, React frontend), andpackages/server/tsconfig.json(extends base, Node backend)—and usereferencesto link the two subprojects.