TypeScript

TypeScript Configuration Deep Dive

40 min Lesson 27 of 40

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:
  1. Create a monorepo structure with 3 packages using project references
  2. Set up path aliases for @components, @utils, and @types
  3. Configure different tsconfig files for development, production, and testing
  4. Enable all strict type-checking options and fix any errors
  5. Use --explainFiles to understand which files are being compiled and why