Understanding tsconfig.json
The tsconfig.json file is the heart of your TypeScript project configuration. It controls how the TypeScript compiler (tsc) transforms your code and what type-checking rules to enforce. Mastering this configuration is crucial for optimal development experience and code quality.
Basic Configuration Structure
{
"compilerOptions": {
// Compiler settings
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"],
"files": ["./path/to/specific/file.ts"],
"extends": "./base-config.json",
"references": [
{ "path": "./packages/core" }
]
}
Target and Module Settings
These determine what JavaScript version and module system your code compiles to:
{
"compilerOptions": {
// JavaScript version to compile to
"target": "ES2022", // ES3, ES5, ES6/ES2015, ES2016...ES2023, ESNext
// Module system
"module": "ESNext", // CommonJS, AMD, UMD, System, ES2015, ES2020, ESNext, NodeNext
// Module resolution strategy
"moduleResolution": "bundler", // node, classic, node16, nodenext, bundler
// Allow default imports from modules with no default export
"allowSyntheticDefaultImports": true,
// Enable interop between CommonJS and ES modules
"esModuleInterop": true,
// Import helpers from tslib
"importHelpers": true,
// Generate corresponding .d.ts files
"declaration": true,
// Generate sourcemap files
"sourceMap": true
}
}
Note: For modern projects using Vite, Webpack, or other bundlers, use "moduleResolution": "bundler" which provides the best developer experience with path aliases and package.json exports.
Strict Type-Checking Options
These are the most important settings for type safety:
{
"compilerOptions": {
// Enable ALL strict checks (recommended)
"strict": true,
// Individual strict flags (enabled when strict: true)
"noImplicitAny": true, // Error on 'any' type inference
"strictNullChecks": true, // null/undefined must be explicit
"strictFunctionTypes": true, // Strict function parameter checking
"strictBindCallApply": true, // Strict bind/call/apply
"strictPropertyInitialization": true, // Class properties must be initialized
"noImplicitThis": true, // Error on 'this' with implied 'any'
"alwaysStrict": true, // Parse in strict mode and emit "use strict"
"useUnknownInCatchVariables": true, // Catch variables are 'unknown' not 'any'
// Additional strict checks
"noUnusedLocals": true, // Error on unused local variables
"noUnusedParameters": true, // Error on unused function parameters
"noImplicitReturns": true, // Error when not all code paths return
"noFallthroughCasesInSwitch": true, // Error on fallthrough switch cases
"noUncheckedIndexedAccess": true, // Add undefined to indexed access
"noImplicitOverride": true, // Require 'override' keyword
"noPropertyAccessFromIndexSignature": true, // Require bracket notation for index access
"exactOptionalPropertyTypes": true // Don't allow undefined for optional props
}
}
Best Practice: Start new projects with "strict": true and all additional strict checks enabled. This catches bugs early and improves code quality dramatically.
Path Mapping and Aliases
Configure module path aliases for cleaner imports:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@types/*": ["src/types/*"],
"@config": ["src/config/index.ts"],
"~/*": ["./src/*"]
}
}
}
Now you can write clean imports:
// Instead of
import { Button } from '../../../components/Button';
import { formatDate } from '../../../utils/date';
// You can write
import { Button } from '@components/Button';
import { formatDate } from '@utils/date';
Warning: Path mapping only affects TypeScript's type checking. Your bundler (Webpack, Vite, etc.) needs separate configuration to resolve these aliases at runtime.
Project References for Monorepos
Use project references to build large codebases efficiently:
Root tsconfig.json:
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" },
{ "path": "./packages/ui" }
]
}
packages/core/tsconfig.json:
{
"compilerOptions": {
"composite": true, // Enable project references
"declaration": true, // Generate .d.ts files
"declarationMap": true, // Generate .d.ts.map files
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
packages/ui/tsconfig.json (depends on core):
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist"
},
"references": [
{ "path": "../core" }
],
"include": ["src/**/*"]
}
Build all projects:
# Build all referenced projects
tsc --build
# Clean and rebuild
tsc --build --clean
tsc --build --force
# Watch mode
tsc --build --watch
Library and Declaration Options
Control which built-in type definitions are available:
{
"compilerOptions": {
// Specify library files to include
"lib": [
"ES2022", // Latest JavaScript features
"DOM", // Browser APIs
"DOM.Iterable", // DOM iterators
"WebWorker" // Web Worker APIs
],
// Type definitions to include (without imports)
"types": [
"node", // Node.js types
"jest", // Jest types
"vite/client" // Vite environment types
],
// Directories to search for type definitions
"typeRoots": [
"./node_modules/@types",
"./src/types"
],
// Skip type checking of declaration files
"skipLibCheck": true
}
}
Note: Set "skipLibCheck": true in almost all projects - it dramatically speeds up compilation by skipping type checking of node_modules declarations.
Emit Options
Control what files TypeScript generates:
{
"compilerOptions": {
// Output directory
"outDir": "./dist",
// Root directory of source files
"rootDir": "./src",
// Remove comments from output
"removeComments": true,
// Don't emit output files
"noEmit": true,
// Don't emit if errors occur
"noEmitOnError": true,
// Import emit helpers from tslib
"importHelpers": true,
// Support JSX
"jsx": "react-jsx", // preserve, react, react-jsx, react-jsxdev, react-native
// Emit design-type metadata for decorators
"emitDecoratorMetadata": true,
// Enable experimental decorators
"experimentalDecorators": true,
// Preserve const enums
"preserveConstEnums": false,
// Generate single file per module
"isolatedModules": true,
// Emit BOM for UTF-8 files
"emitBOM": false,
// Newline character
"newLine": "lf", // lf, crlf
// Strip internal code
"stripInternal": true
}
}
Module Resolution Deep Dive
Understanding how TypeScript resolves modules:
Classic Strategy (deprecated):
// Import: import { x } from "moduleB"
// Resolution order:
1. /root/src/moduleB.ts
2. /root/src/moduleB.d.ts
3. /root/moduleB.ts
4. /root/moduleB.d.ts
5. /moduleB.ts
6. /moduleB.d.ts
Node Strategy (traditional):
// Import: import { x } from "moduleB"
// Resolution order:
1. /root/src/node_modules/moduleB.ts
2. /root/src/node_modules/moduleB/package.json (check "types" field)
3. /root/src/node_modules/moduleB/index.ts
4. /root/node_modules/moduleB.ts
5. ... (continues up directory tree)
Bundler Strategy (modern):
{
"compilerOptions": {
"moduleResolution": "bundler",
// Allow importing .ts files without extension
"allowImportingTsExtensions": true,
// Respect package.json "exports" field
"resolvePackageJsonExports": true,
// Respect package.json "imports" field
"resolvePackageJsonImports": true,
// Allow JSON imports
"resolveJsonModule": true
}
}
Advanced Compiler Options
{
"compilerOptions": {
// Incremental compilation
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo",
// Diagnostics
"diagnostics": false,
"extendedDiagnostics": false,
"listEmittedFiles": false,
"listFiles": false,
// Performance
"assumeChangesOnlyAffectDirectDependencies": true,
// Code generation
"downlevelIteration": true, // Support for-of on older targets
"inlineSourceMap": false,
"inlineSources": false,
"declarationMap": true,
// Module detection
"moduleDetection": "auto", // auto, legacy, force
// Type acquisition
"disableSizeLimit": false,
// Verbosity
"suppressExcessPropertyErrors": false,
"suppressImplicitAnyIndexErrors": false,
// Compatibility
"allowUmdGlobalAccess": false,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"forceConsistentCasingInFileNames": true
}
}
Watch Mode Configuration
{
"watchOptions": {
// Watch strategy
"watchFile": "useFsEvents", // fixedPollingInterval, priorityPollingInterval,
// dynamicPriorityPolling, useFsEvents, useFsEventsOnParentDirectory
"watchDirectory": "useFsEvents",
// Polling intervals (when using polling)
"fallbackPolling": "dynamicPriority",
"synchronousWatchDirectory": true,
// Exclude directories from watching
"excludeDirectories": ["**/node_modules", "_build"],
"excludeFiles": ["build/fileWhichChangesOften.ts"]
}
}
Environment-Specific Configurations
Base configuration (tsconfig.base.json):
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
}
}
Development config (tsconfig.json):
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"incremental": true,
"sourceMap": true
},
"include": ["src/**/*", "tests/**/*"]
}
Production build config (tsconfig.build.json):
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"removeComments": true,
"sourceMap": false
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
}
Node.js Native ESM Configuration
For Node.js projects using native ESM (package.json with "type": "module"):
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext", // or "Node16"
"moduleResolution": "NodeNext", // or "Node16"
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"isolatedModules": true
}
}
With this configuration, you must use explicit file extensions:
// ✓ Correct
import { myFunc } from './utils.js'; // Note: .js, not .ts!
// ✗ Wrong
import { myFunc } from './utils';
React Project Configuration
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"jsx": "react-jsx", // or "react-jsxdev" for development
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
Debugging Configuration Issues
Use these compiler flags to diagnose problems:
# Show all compiler options
tsc --showConfig
# Explain why files are included
tsc --explainFiles
# Show detailed resolution traces
tsc --traceResolution
# List all emitted files
tsc --listEmittedFiles
# Show extended diagnostics
tsc --extendedDiagnostics
# Generate type trace
tsc --generateTrace ./trace
Exercise:
- Create a monorepo structure with 3 packages using project references
- Set up path aliases for @components, @utils, and @types
- Configure different tsconfig files for development, production, and testing
- Enable all strict type-checking options and fix any errors
- Use
--explainFiles to understand which files are being compiled and why