TypeScript Compiler & Configuration
Understanding the TypeScript Compiler
The TypeScript compiler (tsc) is the tool that transforms your TypeScript code into JavaScript. Understanding how to configure and control the compiler is essential for building robust TypeScript applications. The compiler does much more than just remove type annotations—it can check for errors, transform modern syntax, optimize output, and enforce coding standards.
The tsconfig.json File
The tsconfig.json file is the heart of any TypeScript project. It tells the compiler how to process your code and what rules to enforce. When you run tsc without any arguments in a directory containing a tsconfig.json file, the compiler uses the settings from that file.
Creating a tsconfig.json
The easiest way to create a tsconfig.json file is to use the --init flag:
tsc --init
This creates a tsconfig.json file with many options commented out. Let's start with a minimal configuration and build from there:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
Important: The tsconfig.json file uses JSON format, which means no trailing commas and all keys must be in double quotes. Comments are supported using // or /* */ syntax, even though standard JSON doesn't allow comments.
Essential Compiler Options
Target and Module
These two options control what JavaScript version the compiler outputs:
target
Specifies which ECMAScript version to compile to. Common values:
"target": "ES5" // For older browsers (IE11) "target": "ES6" // Also known as ES2015 "target": "ES2020" // Modern JavaScript "target": "ESNext" // Latest features
Recommendation: Use "ES2020" or "ES2021" for modern applications. Use "ES5" only if you need to support very old browsers.
module
Specifies the module system for the output:
"module": "commonjs" // Node.js style (require/module.exports) "module": "es2015" // Modern ES modules (import/export) "module": "esnext" // Latest module features "module": "amd" // For browser module loaders "module": "umd" // Universal module definition
Common Setup: For Node.js projects, use "commonjs". For modern frontend projects with bundlers like Webpack or Vite, use "esnext" or "es2015".
Output Control
outDir and rootDir
Control where compiled files are placed:
{
"compilerOptions": {
"rootDir": "./src", // Where TypeScript files are located
"outDir": "./dist" // Where compiled JavaScript goes
}
}
With this configuration, a file at src/utils/helper.ts compiles to dist/utils/helper.js, preserving the directory structure.
Other Output Options
{
"compilerOptions": {
"removeComments": true, // Remove comments from output
"sourceMap": true, // Generate .map files for debugging
"declaration": true, // Generate .d.ts type definition files
"declarationMap": true, // Generate source maps for .d.ts files
"inlineSourceMap": false, // Include source maps inline
"outFile": "./dist/bundle.js" // Concatenate all output into one file
}
}
Warning: The outFile option only works with "module": "amd" or "module": "system". For most modern projects, use a bundler like Webpack instead of outFile.
Module Resolution
{
"compilerOptions": {
"moduleResolution": "node", // Use Node.js module resolution
"baseUrl": "./", // Base directory for module resolution
"paths": { // Path mapping for imports
"@utils/*": ["src/utils/*"],
"@models/*": ["src/models/*"],
"@components/*": ["src/components/*"]
},
"esModuleInterop": true, // Enable interop between CommonJS and ES modules
"allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export
"resolveJsonModule": true // Allow importing .json files
}
}
With path mapping configured, you can import like this:
// Instead of relative imports
import { formatDate } from '../../../utils/dateFormatter';
// Use clean path aliases
import { formatDate } from '@utils/dateFormatter';
Note: Path mapping in tsconfig.json is only for TypeScript compilation. If you use a bundler, you'll need to configure the same paths there (e.g., in Webpack's resolve.alias).
Strict Mode Options
TypeScript's strict mode enables all strict type-checking options. This is highly recommended for new projects:
{
"compilerOptions": {
"strict": true
}
}
Setting "strict": true enables all of these options:
Individual Strict Options
strictNullChecks
When enabled, null and undefined are not in the type of every value:
// Without strictNullChecks let name: string = null; // OK // With strictNullChecks let name: string = null; // Error: Type 'null' is not assignable to type 'string' let name: string | null = null; // OK - explicitly allow null
strictFunctionTypes
Enables stricter checking of function parameter types:
type Logger = (message: string | number) => void;
// Without strictFunctionTypes - OK
const numberLogger: Logger = (message: number) => {
console.log(message);
};
// With strictFunctionTypes - Error
// Function parameters must be compatible
strictBindCallApply
Checks that bind, call, and apply methods are invoked with correct arguments:
function greet(name: string, age: number) {
console.log(`Hello ${name}, you are ${age} years old`);
}
// With strictBindCallApply
greet.call(null, "Alice", 25); // OK
greet.call(null, "Alice", "25"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
greet.apply(null, ["Alice"]); // Error: Expected 2 arguments, but got 1
noImplicitAny
Raises an error when TypeScript can't infer a type and defaults to any:
// Without noImplicitAny - OK
function log(message) { // message is implicitly 'any'
console.log(message);
}
// With noImplicitAny - Error
function log(message) { // Error: Parameter 'message' implicitly has an 'any' type
console.log(message);
}
// Fixed
function log(message: string) { // OK
console.log(message);
}
noImplicitThis
Raises an error when this has an implicit any type:
// Without noImplicitThis
const obj = {
name: "Alice",
greet() {
return function() {
console.log(this.name); // 'this' is implicitly 'any'
};
}
};
// With noImplicitThis - must specify this type
const obj = {
name: "Alice",
greet() {
return function(this: { name: string }) {
console.log(this.name);
};
}
};
alwaysStrict
Ensures all files are parsed in ECMAScript strict mode and emits "use strict" in each output file:
// Output JavaScript will include: "use strict"; // This prevents common JavaScript pitfalls like: x = 10; // Error: Cannot assign to undeclared variable
Best Practice: Always enable strict mode for new projects. It catches many potential bugs and enforces better coding practices. For existing JavaScript projects being migrated to TypeScript, you might start with strict mode disabled and gradually enable strict options one at a time.
Other Important Options
Code Quality Options
{
"compilerOptions": {
"noUnusedLocals": true, // Error on unused local variables
"noUnusedParameters": true, // Error on unused function parameters
"noImplicitReturns": true, // Error when not all code paths return a value
"noFallthroughCasesInSwitch": true, // Error on fallthrough cases in switch
"allowUnreachableCode": false, // Error on unreachable code
"allowUnusedLabels": false // Error on unused labels
}
}
Library and Type Definition Options
{
"compilerOptions": {
"lib": ["ES2020", "DOM"], // Type definitions to include
"types": ["node", "jest"], // Specific @types packages to include
"skipLibCheck": true // Skip type checking of declaration files
}
}
The lib option is crucial—it tells TypeScript what built-in APIs are available:
// For Node.js projects "lib": ["ES2020"] // For browser projects "lib": ["ES2020", "DOM", "DOM.Iterable"] // For projects that need both "lib": ["ES2020", "DOM", "WebWorker"]
Experimental and Advanced Options
{
"compilerOptions": {
"experimentalDecorators": true, // Enable experimental decorator syntax
"emitDecoratorMetadata": true, // Emit metadata for decorators
"forceConsistentCasingInFileNames": true, // Ensure consistent file name casing
"isolatedModules": true, // Ensure each file can be safely transpiled
"jsx": "react", // JSX mode for React
"incremental": true, // Enable incremental compilation for faster builds
"tsBuildInfoFile": "./dist/.tsbuildinfo" // Where to store incremental build info
}
}
Include and Exclude
Control which files the compiler processes:
{
"include": [
"src/**/*" // Include all files in src and subdirectories
],
"exclude": [
"node_modules", // Exclude dependencies
"**/*.spec.ts", // Exclude test files
"**/*.test.ts", // Exclude test files
"dist" // Exclude compiled output
]
}
Default Behavior: If you don't specify include, TypeScript includes all .ts, .tsx, and .d.ts files in the project. If you don't specify exclude, it automatically excludes node_modules, bower_components, and the output directory.
Complete Real-World Example
Here's a comprehensive tsconfig.json for a modern Node.js project:
{
"compilerOptions": {
// Language and Environment
"target": "ES2020",
"lib": ["ES2020"],
"module": "commonjs",
"moduleResolution": "node",
// Output
"rootDir": "./src",
"outDir": "./dist",
"removeComments": true,
"sourceMap": true,
"declaration": true,
// Type Checking
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// Module Resolution
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true,
// Performance
"skipLibCheck": true,
"incremental": true,
// Path Mapping
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@utils/*": ["src/utils/*"],
"@models/*": ["src/models/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
}
Compiler CLI Usage
Basic Commands
// Compile with tsconfig.json tsc // Compile specific file tsc file.ts // Compile with custom config tsc --project tsconfig.production.json // Watch mode tsc --watch // Show compiler version tsc --version // Show help tsc --help
Overriding Config Options
You can override tsconfig.json options from the command line:
// Override target tsc --target ES5 // Override outDir tsc --outDir ./build // Enable source maps tsc --sourceMap true // Compile despite errors tsc --noEmitOnError false
Multiple Configuration Files
For complex projects, you might have different configurations for different environments:
// tsconfig.json (base configuration)
{
"compilerOptions": {
"strict": true,
"target": "ES2020"
}
}
// tsconfig.dev.json (development)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": true,
"incremental": true
}
}
// tsconfig.prod.json (production)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"removeComments": true,
"sourceMap": false
}
}
Use them with:
tsc --project tsconfig.dev.json tsc --project tsconfig.prod.json
Practice Exercise
Create a TypeScript project with proper configuration:
- Create a new directory called
typescript-config-practice - Initialize npm:
npm init -y - Install TypeScript:
npm install --save-dev typescript - Create a
tsconfig.jsonwith:- Strict mode enabled
- ES2020 target
- Source maps enabled
- src/ as root directory
- dist/ as output directory
- Create
src/index.tswith:interface User { id: number; name: string; email: string; } function getUser(id: number): User { return { id, name: "John Doe", email: "john@example.com" }; } const user = getUser(1); console.log(`User: ${user.name} (${user.email})`); - Compile using
npx tsc - Run the compiled code:
node dist/index.js - Try removing the email property from the returned object and see what error TypeScript gives
- Enable watch mode and make changes to see automatic recompilation
Summary
- The
tsconfig.jsonfile controls how TypeScript compiles your code targetandmodulecontrol the JavaScript version and module systemoutDirandrootDircontrol file organization"strict": trueenables all strict type-checking options and is highly recommended- Path mapping allows clean imports without complex relative paths
- The compiler can generate source maps, declaration files, and optimize output
- You can have multiple config files for different environments using
extends - The TypeScript compiler CLI provides many options for compilation and error checking
Next Steps: Now that you understand how to configure TypeScript, we'll explore the type system in detail, starting with basic types in the next lesson.