# TypeScript Configuration (`tsconfig.json`)

## 1. What `tsconfig.json` Controls

`tsconfig.json` configures every aspect of how `tsc` parses, checks, and emits your project — which files are included, which JavaScript version to target, how strict the checker should be, and where compiled output goes.

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

## 2. `target` — Which JavaScript Version to Emit

```json
{ "compilerOptions": { "target": "ES2015" } }
```

Controls how modern the *output* JavaScript syntax is allowed to be — e.g. whether `class` syntax is emitted directly (ES2015+) or downleveled into constructor-function-and-prototype code (for older targets, matching the JavaScript Prototypes topic's pre-class patterns). Set this to match the oldest runtime your compiled code actually needs to support.

## 3. `module` — Which Module System to Emit

```json
{ "compilerOptions": { "module": "CommonJS" } }
```

Controls whether `import`/`export` compiles to Node's `require`/`module.exports` (`CommonJS`) or stays as native ES module syntax (`ESNext`/`ES2020`) — see the Modules topic for the full contrast between these two systems.

## 4. `strict` — The Big Safety Switch

```json
{ "compilerOptions": { "strict": true } }
```

`strict` isn't one setting — it's a bundle that turns on roughly eight individual stricter-checking flags at once (full list in "Under the Hood"), including `strictNullChecks` (the single most impactful one: without it, `null`/`undefined` are silently assignable to every type, defeating most of what makes the Union Types and Type Guards topics' patterns actually safe). Virtually every real-world TypeScript project should enable `strict`.

## 5. `outDir` and `rootDir`

```json
{ "compilerOptions": { "outDir": "./dist", "rootDir": "./src" } }
```

`rootDir` marks where your source tree starts; `outDir` is where compiled `.js` (and `.d.ts`, if `declaration: true`) files get written, mirroring the source tree's folder structure underneath it.

## 6. `include` / `exclude` / `files`

```json
{
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"],
  "files": ["src/entry.ts"]
}
```

`include` (glob patterns) and `exclude` (glob patterns to remove from that set) control which files `tsc` considers part of the project; `files` is an explicit, non-glob list, usually used only for very small or single-entry-point projects.

## 7. `extends` — Sharing Configuration

```json
// tsconfig.base.json
{ "compilerOptions": { "strict": true, "target": "ES2020" } }
```

```json
// tsconfig.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": { "outDir": "./dist" }
}
```

`extends` lets multiple projects (or multiple build targets within one project — e.g. a separate test config) share a common base configuration, overriding only what differs.

## 8. `esModuleInterop` and `skipLibCheck` — Two Common Practical Flags

```json
{ "compilerOptions": { "esModuleInterop": true, "skipLibCheck": true } }
```

`esModuleInterop` smooths over interoperability friction between CommonJS and ES module `import`/`export default` conventions (lets `import React from "react"` work cleanly against CommonJS-authored packages). `skipLibCheck` skips type-checking `.d.ts` files themselves (including ones in `node_modules`) — a common, safe performance optimization, since you rarely need to re-verify a third-party library's own shipped type declarations.

## 9. Under the Hood: What `strict` and `target` Actually Expand Into

### `strict: true` is genuinely just shorthand for several independent flags

```
strict: true  is equivalent to setting ALL of:
  strictNullChecks           strictFunctionTypes
  strictBindCallApply         strictPropertyInitialization
  noImplicitAny                 noImplicitThis
  alwaysStrict                   useUnknownInCatchVariables
```

Each of these can also be set individually — `strict: true` is a convenience bundle, not a single atomic behavior. This matters practically because you can enable `strict` broadly and then selectively turn OFF one specific sub-flag (e.g. `"strictPropertyInitialization": false`) if one particular rule doesn't fit a specific codebase yet, without losing the rest of strict mode's protection.

### `target` affects two genuinely independent things, not just syntax

Most people think of `target` as only controlling *syntax downleveling* (whether `class`/arrow functions/etc. get transformed for older engines) — but it also determines **which built-in global types are available to the checker at all**, via which `lib.d.ts` files get included by default.

```json
{ "compilerOptions": { "target": "ES5" } }
```

```ts
const map = new Map(); // ❌ error under target: ES5, unless `lib` is explicitly extended —
                          // ES5's default lib doesn't include Map's type declarations at all,
                          // because Map didn't exist in the ES5 JavaScript specification
```

![target ES5 causes both syntax downleveling and a smaller set of available lib.d.ts globals like Map](diagrams/01-target-two-effects.svg)

This is why upgrading `target` can sometimes make previously-valid code fail to compile — not because the syntax changed, but because the set of globally-available *type declarations* (from `lib.d.ts` and friends) is tied to which JS specification version `target` implies, separately from the syntax-emission behavior. The `lib` compiler option lets you decouple these two concerns explicitly when needed (e.g. targeting old syntax while still declaring access to modern runtime globals a polyfill provides).

### `extends` performs a genuine deep merge, not a full replacement

When a config `extends` a base config, `tsc` doesn't just use one or the other — it merges `compilerOptions` key by key, with the extending file's values taking priority for anything it explicitly sets, while everything unmentioned falls through from the base config unchanged. This is why the section 7 example project can inherit `strict` and `target` from the base while only adding its own `outDir`, without repeating the inherited settings.

### `include`/`exclude` are resolved into a concrete file list before checking begins

Before any type-checking happens, `tsc` resolves the glob patterns in `include`/`exclude` into a concrete, final list of source files — this resolved file set, together with anything pulled in transitively via `import` statements from those files, is exactly what gets parsed, bound, and checked (the same pipeline from the Type System Fundamentals topic), and exactly what "the project" means for purposes of cross-file features like declaration merging and global ambient declarations.

## 10. Common Pitfalls

1. **Leaving `strict` off "for now" in a new project** — retrofitting strict mode onto an already-large codebase later is far more painful than starting with it enabled from day one.
2. **Setting `target` without considering `lib`** — using modern built-ins (`Map`, `Promise`, `Array.prototype.flat`) while targeting an old JS version can fail to type-check even though the actual runtime has a polyfill providing them; adjust `lib` explicitly in that case.
3. **Assuming `exclude` prevents a file from being checked if it's still reachable via `import`** — `exclude` only affects the initial file-discovery glob; a file imported from an included file is still pulled in and checked regardless of `exclude`.
4. **Forgetting `extends` merges rather than replaces** — expecting to have to fully re-specify every option in an extending config, when only the differences need to be listed.

## 11. Quick Reference

```
target        → output JS syntax version AND which lib.d.ts globals are available
module          → import/export vs require/module.exports emission
strict           → bundle of ~8 stricter-checking flags, expand individually if needed
outDir/rootDir     → where compiled output goes / where source starts
include/exclude      → which files are part of the project (resolved before checking begins)
extends                → deep-merges a base config, override only what differs
```
