TypeScript tsconfig Deep Dive — Complete Guide
In this tutorial, you will learn about TypeScript tsconfig Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.
The tsconfig.json file is the control center of every TypeScript project — it configures compilation targets, module systems, strictness levels, output directories, and dozens of options that determine how the TypeScript compiler processes your code.
What You'll Learn
- Every strict mode flag explained
- target vs module differences
- Module resolution strategies
- Source maps, declarations, and output control
- Recommended configurations for different project types
Why It Matters
The wrong tsconfig can silently hide real type errors (noImplicitAny off), produce bloated output (target ES5 unnecessarily), or fail to compile on other machines (missing moduleResolution). Understanding tsconfig is essential for every TypeScript developer.
Real-World Use
DodaTech's standard tsconfig template is reviewed quarterly across all projects. A misconfigured strict: false once let a null-reference bug slip into the Doda Browser extension, causing crashes on Firefox. Now strict mode is non-negotiable.
Learning Path
flowchart LR A[Branded Types] --> B[tsconfig Deep Dive] B --> C[Project References] B --> D[You Are Here] C --> E[Module Resolution] E --> F[Source Maps]
The strict Family
Enabling strict: true turns on all these checks:
strictNullChecks
When accessing a value that could be null or undefined, TypeScript errors:
// With strictNullChecks: true
function getLength(s: string | null): number {
// return s.length; // Error: Object is possibly 'null'
if (s === null) return 0;
return s.length; // OK — narrowed
}
noImplicitAny
Errors when TypeScript cannot infer a type and defaults to any:
// With noImplicitAny: true
function process(value) { } // Error: Parameter 'value' implicitly has 'any' type
strictFunctionTypes
Makes function parameter types contravariant (sound):
// With strictFunctionTypes: true
type Fn1 = (x: string | number) => void;
type Fn2 = (x: string) => void;
let f1: Fn1 = (x) => {};
let f2: Fn2 = (x) => {};
// f1 = f2; // Error: not assignable
strictBindCallApply
Types .bind, .call, and .apply correctly:
function greet(name: string, age: number): string {
return `${name} is ${age}`;
}
const bound = greet.bind(null, "Alice"); // type: (age: number) => string
strictPropertyInitialization
Ensures class properties are initialized:
class User {
name: string; // Error: not initialized
// Fix: name: string = ""; or name!: string (definite assignment)
}
noImplicitThis
Errors when this is used without a type:
function onClick() {
console.log(this); // Error: 'this' implicitly has type 'any'
}
alwaysStrict
Adds "use strict" to all output files.
target
Controls which ECMAScript features are allowed in source and how features are downleveled:
{
"compilerOptions": {
"target": "ES2022"
}
}
| target | Features Allowed | Output Style |
|---|---|---|
| ES5 | Limited | Heavy polyfilling |
| ES2015 | classes, arrow functions | Moderate |
| ES2020 | optional chaining, nullish coalescing | Minimal |
| ES2022 | class fields, top-level await | Almost none |
| ESNext | Latest proposals | No downleveling |
Recommendation: Use ES2022 for modern Node.js or browser targets. Use ESNext if you use a bundler.
module
Controls the module system in output:
{
"compilerOptions": {
"module": "ESNext"
}
}
| module | Output Format | Use When |
|---|---|---|
| CommonJS | require/module.exports | Node.js without ESM |
| ESNext | import/export | Bundlers (Webpack, Vite) |
| Node16 | Mixed CJS/ESM | Node.js 16+ with type: module |
| NodeNext | Like Node16 | Latest Node.js |
| UMD | Universal | Library distribution |
Module Resolution
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}
| Resolution | Use Case |
|---|---|
| classic | Legacy (avoid) |
| node | Node.js CJS |
| node16 | Node.js ESM/CJS |
| nodenext | Latest Node.js |
| bundler | Vite, Webpack, Esbuild |
Paths and Base URL
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@utils/*": ["src/utils/*"]
}
}
}
Now you can import: import { foo } from "@/utils/helpers" instead of relative paths.
Output Control
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": true
}
}
| Option | Purpose |
|---|---|
| outDir | Where compiled JS goes |
| rootDir | Where TS source is |
| declaration | Generate .d.ts files |
| declarationMap | Map .d.ts to .ts sources |
| sourceMap | Debug TS directly |
| removeComments | Strip comments from output |
Common Mistakes
1. Not Enabling strict Mode
strict: true catches more bugs. There is almost no reason to disable it.
2. Confusing target and module
target controls language features. module controls module format. They are independent.
3. Missing skipLibCheck
Without it, TypeScript checks all @types/* packages, slowing compilation.
4. Not Using noUnusedLocals and noUnusedParameters
These catch dead code:
{ "noUnusedLocals": true, "noUnusedParameters": true }
5. Wrong moduleResolution for the Environment
Using node resolution with a bundler causes issues. Use bundler.
Practice Questions
What does
strict: trueenable? strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict.What is the difference between
targetandmodule? target controls language features (async/await, arrows). module controls module format (CommonJS, ESM).What does
skipLibCheckdo? Skips Type Checking of.d.tsfiles in node_modules, speeding up compilation.Why use
baseUrlandpaths? To create clean import aliases instead of deep relative paths.
Challenge: Starting from tsc --init, modify the generated tsconfig to target a Node.js 20 project using ESM, with strict mode, source maps, and @/ path aliases.
FAQ
Recommended Configurations
Node.js Library
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
React Application
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "react-jsx",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
What's Next
Now explore project references for multi-project setups:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/30-branded-types" >}} | Review branded types |
| {{< ref "/programming-languages/typescript/32-project-references" >}} | Composite projects, references, build mode |
| {{< ref "/programming-languages/typescript/33-module-resolution" >}} | Classic vs node, paths, baseUrl |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro