Table of Contents
- What is
tsconfig.json? - Creating a
tsconfig.json - Core Compiler Options
- Project References
- Includes, Excludes, and Files
- Type Checking Strictness
- Advanced Configuration Scenarios
- Best Practices
- Conclusion
- References
What is tsconfig.json?
The tsconfig.json file is the configuration hub for the TypeScript compiler. It defines:
- Which files TypeScript should process (via
includes,excludes, orfiles). - How the compiler should transpile TypeScript to JavaScript (e.g., target ECMAScript version).
- Strictness rules for type checking (e.g., disallowing
anytypes). - Output settings (e.g., where to place compiled files).
- Advanced features like project references for monorepos.
Without tsconfig.json, tsc will use default settings, which may not align with your project’s goals (e.g., targeting an outdated ES version or skipping strict type checks).
Creating a tsconfig.json
There are two ways to create a tsconfig.json:
1. Manual Creation
Create an empty tsconfig.json file in your project root and add options incrementally. Start with a minimal setup:
{
"compilerOptions": {
"target": "ES6",
"module": "ESNext",
"strict": true
}
}
2. Auto-Generate with tsc --init
The easiest way is to use TypeScript’s built-in initializer. Run:
tsc --init
This generates a commented tsconfig.json with all possible options (over 50!), making it a great reference. Most options are disabled by default (commented out), so you can uncomment and tweak as needed.
Core Compiler Options
The compilerOptions object is the soul of tsconfig.json. It controls how TypeScript compiles and checks your code. Let’s break down the most critical options by category.
Target & Module
These options define how TypeScript transpiles code and structures modules.
| Option | Purpose | Common Values |
|---|---|---|
target | Specifies the ECMAScript version to transpile to (e.g., ES5, ES2020). | ES5, ES6, ES2020, ESNext |
module | Defines the module system for output (e.g., CommonJS for Node, ES6 for browsers). | CommonJS, ES6, ESNext, UMD |
Example: Target modern browsers with ES modules:
{
"compilerOptions": {
"target": "ES2020", // Transpile to ES2020 syntax
"module": "ESNext" // Use ES modules (import/export)
}
}
Module Resolution
TypeScript needs to resolve module imports (e.g., import { utils } from './helpers'). The moduleResolution option controls this logic.
| Option | Purpose |
|---|---|
moduleResolution | Algorithm for resolving modules. Node mimics Node.js resolution; Classic is legacy. |
baseUrl | Base directory for resolving non-relative module names (e.g., import 'utils'). |
paths | Map of module names to file paths (useful for aliases, e.g., @src/*). |
Example: Alias @src to your source directory:
{
"compilerOptions": {
"baseUrl": ".", // Resolve from project root
"paths": {
"@src/*": ["src/*"] // `import { App } from '@src/App'` → `src/App.ts`
}
}
}
Strictness Flags
Strict type checking is TypeScript’s superpower. The strict flag enables a suite of strictness rules. For granular control, you can disable strict and enable individual flags:
| Flag | Purpose |
|---|---|
strict | Enables all strictness flags (recommended for new projects). |
noImplicitAny | Disallows variables/parameters with implicit any type (e.g., function foo(x) {}). |
strictNullChecks | Requires explicit handling of null/undefined (e.g., `string |
strictFunctionTypes | Enforces stricter function parameter type checking. |
Example: Strict mode with exceptions:
{
"compilerOptions": {
"strict": true, // Enable most strict flags
"noUnusedParameters": false // Allow unused parameters (temporarily)
}
}
Emit Options
These options control where and how compiled JavaScript is output.
| Option | Purpose |
|---|---|
outDir | Directory to place compiled JavaScript files (avoids cluttering source). |
rootDir | Root directory of TypeScript source files (ensures output mirrors source structure). |
noEmit | Disable emitting files (use only for type checking, e.g., with Babel). |
Example: Organize output in a dist folder:
{
"compilerOptions": {
"rootDir": "./src", // Source files live in src/
"outDir": "./dist", // Compiled files go to dist/
"removeComments": true // Strip comments from output
}
}
Source Maps & Debugging
Source maps map compiled JavaScript back to original TypeScript, making debugging easier.
| Option | Purpose |
|---|---|
sourceMap | Generate .map files for debugging. |
inlineSourceMap | Embed source maps directly in JS files (small projects). |
Example: Enable source maps for production debugging:
{
"compilerOptions": {
"sourceMap": true,
"inlineSources": true // Embed original TypeScript in source maps
}
}
Type Checking Rules
Additional flags to enforce code quality:
| Option | Purpose |
|---|---|
noUnusedLocals | Error on unused variables (avoids dead code). |
noUnusedParameters | Error on unused function parameters. |
noImplicitReturns | Require all code paths in functions to return a value. |
Project References
For large projects or monorepos, project references let you split code into smaller, independent sub-projects. This speeds up builds (incremental compilation) and enforces clear boundaries.
How to Use Project References
- Create sub-projects with their own
tsconfig.json(e.g.,tsconfig.utils.json). - Reference sub-projects in the root
tsconfig.jsonvia thereferencesarray.
Example: Root tsconfig.json referencing a utils sub-project:
{
"references": [
{ "path": "./utils" } // Path to sub-project (contains tsconfig.json)
]
}
Sub-project utils/tsconfig.json:
{
"compilerOptions": {
"composite": true, // Required for project references
"outDir": "../dist/utils"
},
"include": ["src/**/*"]
}
Includes, Excludes, and Files
These options control which files the TypeScript compiler processes.
| Option | Purpose |
|---|---|
includes | Array of glob patterns to include (e.g., ["src/**/*"]). |
excludes | Array of glob patterns to exclude (defaults to node_modules, bower_components). |
files | Explicit list of files to include (overrides includes/excludes). |
Example: Include src/ and tests/, exclude src/vendor/:
{
"include": ["src/**/*", "tests/**/*"], // **/* = all files/subfolders
"exclude": ["src/vendor/**/*"] // Exclude third-party code
}
Type Checking Strictness: Deep Dive
Strict mode is transformative, but its flags can be overwhelming. Let’s unpack key strictness rules with examples.
strictNullChecks
Without strictNullChecks, null and undefined are considered subtypes of all types, leading to silent bugs:
// ❌ Without strictNullChecks: No error (but runtime crash!)
const name: string = null;
console.log(name.toUpperCase()); // Throws "Cannot read property 'toUpperCase' of null"
With strictNullChecks, null/undefined must be explicit:
// ✅ With strictNullChecks: Explicit union type
const name: string | null = null;
if (name) { // Check for null before using
console.log(name.toUpperCase());
}
noImplicitAny
Without noImplicitAny, TypeScript infers any for untyped variables, losing type safety:
// ❌ Without noImplicitAny: x is implicitly 'any'
function log(x) {
x.toUpperCase(); // No error, even if x is a number!
}
log(123); // Runtime error: 123.toUpperCase is not a function
With noImplicitAny, you must explicitly type variables:
// ✅ With noImplicitAny: Explicit type
function log(x: string) {
x.toUpperCase(); // Safe!
}
log(123); // ❌ Error: Argument of type 'number' is not assignable to 'string'
Advanced Configuration Scenarios
Extending Configs with extends
Avoid duplicating configs across projects by extending a base tsconfig.json. For example, create tsconfig.base.json and inherit from it:
tsconfig.base.json:
{
"compilerOptions": {
"target": "ES2020",
"strict": true,
"sourceMap": true
}
}
Project-specific tsconfig.json:
{
"extends": "./tsconfig.base.json", // Inherit base settings
"compilerOptions": {
"outDir": "./dist" // Override base outDir
},
"include": ["src/**/*"]
}
Development vs. Production Configs
Use separate configs for dev (e.g., source maps) and production (e.g., minification).
tsconfig.dev.json:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"sourceMap": true,
"noUnusedLocals": false // Tolerate unused variables in dev
}
}
tsconfig.prod.json:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"sourceMap": false, // No source maps in production
"removeComments": true
}
}
Run with:
tsc -p tsconfig.dev.json # Development build
tsc -p tsconfig.prod.json # Production build
Integration with Tools (Webpack/Babel)
For projects using Webpack or Babel, TypeScript often handles type checking while Babel/Webpack handles transpilation.
Example: Webpack + TypeScript
Use ts-loader or babel-loader with transpileOnly: true for faster builds, and run tsc --noEmit separately for type checks:
// tsconfig.json (type checking only)
{
"compilerOptions": {
"noEmit": true, // Disable output (Webpack handles transpilation)
"strict": true
}
}
Best Practices
- Enable
strict: truefor new projects. It catches bugs early and enforces clean code. - Use
extendsto share configs across projects (e.g.,tsconfig.base.json). - Avoid
excludeunless necessary—includesis more explicit. - Set
rootDirandoutDirto keep source and output files separate. - Use project references for monorepos to speed up builds.
- Commit
tsconfig.jsonto version control for consistency across teams.
Conclusion
The tsconfig.json file is your key to unlocking TypeScript’s full potential. By tailoring its options, you can enforce type safety, optimize builds, and align with your project’s needs—whether you’re building a small app or a large monorepo. Start with strict: true, experiment with flags, and leverage advanced features like project references to scale.