tsconfig & Compiler Options

10 questions found

What is the purpose of the tsconfig.json file, and what does the 'compilerOptions' key control?

Beginner
tsconfig.json marks a directory as the root of a TypeScript project and configures how the compiler behaves — 'compilerOptions' specifically holds settings controlling type-checking strictness, the JavaScript output target/format, module resolution, and where compiled files are written.
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true
  },
  "include": ["src/**/*"]
}
Real-world example Setting up a new TypeScript project's baseline configuration for compiling and type-checking source files.

Common follow-ups: What happens if you run 'tsc' in a directory with no tsconfig.json present at all?

Modules

What does the 'target' compiler option control, and how does it affect which JavaScript features get downleveled?

Beginner
'target' specifies which ECMAScript version the compiled JavaScript output should conform to — newer language features (like optional chaining, async/await, or classes) get automatically transformed ('downleveled') into older equivalent syntax if the target doesn't natively support them, ensuring compatibility with older runtimes.
// target: 'ES5' downlevels arrow functions, classes, async/await, etc. into ES5-compatible code
// target: 'ES2020' keeps most modern syntax as-is, assuming a modern runtime
Real-world example Choosing 'ES2020' or newer for a modern browser/Node.js target versus 'ES5' for supporting very old browsers like IE11.

Common follow-ups: Does a higher 'target' setting affect TYPE CHECKING strictness, or only the shape of the emitted JavaScript?

Modules

What does the 'strict' compiler option actually enable, and which individual flags does it bundle together?

Intermediate
'strict': true is a shorthand that enables a whole family of individual strictness flags at once, including noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict, and useUnknownInCatchVariables — collectively representing TypeScript's recommended, most type-safe configuration.
// Equivalent to manually setting all of these to true:
{
  "compilerOptions": {
    "strict": true
    // implies: noImplicitAny, strictNullChecks, strictFunctionTypes,
    // strictBindCallApply, strictPropertyInitialization, noImplicitThis,
    // alwaysStrict, useUnknownInCatchVariables
  }
}
Real-world example Enabling the single 'strict' flag as the recommended baseline for any new TypeScript project, rather than manually toggling each sub-flag.

Common follow-ups: Can you enable 'strict': true globally but explicitly opt OUT of just one specific sub-flag?

Migrating JavaScript to TypeScript

What is the difference between the 'include'/'exclude' arrays and the 'files' array in tsconfig.json for specifying which files are part of the compilation?

Intermediate
'include' specifies glob patterns for files/directories to bring INTO the project (with 'exclude' subtracting specific patterns back out, commonly node_modules by default); 'files' instead lists an EXPLICIT, exhaustive array of individual file paths — useful for very small projects, but impractical to maintain manually as a project grows.
{
  "include": ["src/**/*.ts"],
  "exclude": ["src/**/*.test.ts", "node_modules"]
}
// vs.
{
  "files": ["src/index.ts", "src/utils.ts"] // must list every file explicitly
}
Real-world example Excluding test files or generated code from the main application's production TypeScript compilation.

Common follow-ups: What is included by default if NEITHER 'include' nor 'files' is specified at all?

Modules

What do 'noUnusedLocals' and 'noUnusedParameters' do, and how do you intentionally exempt a genuinely unused parameter?

Intermediate
Both flags flag declared-but-never-used local variables or function parameters as compile errors, helping catch dead code and leftover debugging variables; to intentionally allow an unused parameter (like a required positional parameter you don't need), prefix its name with an underscore, which the compiler specifically exempts from this check.
// tsconfig.json: { "noUnusedParameters": true }
function handler(event: Event, _index: number) {
  console.log(event); // '_index' is unused but exempted due to the underscore prefix
}
Real-world example Catching leftover debugging variables or genuinely dead code left behind during refactoring, enforced automatically in CI.

Common follow-ups: Does this underscore-prefix exemption apply to unused LOCAL VARIABLES too, or only function parameters?

Migrating JavaScript to TypeScript

How do TypeScript project references ('references' + 'composite': true) improve build performance and enforce boundaries in a large monorepo?

Advanced
Project references let you split a large codebase into independently-buildable sub-projects with explicit declared dependencies between them; TypeScript then performs INCREMENTAL builds (via 'tsc --build'), only recompiling projects whose dependencies actually changed, and enforces that a project can only import from packages it explicitly references — catching accidental circular or unauthorized cross-package imports at the project-structure level.
// packages/shared/tsconfig.json
{ "compilerOptions": { "composite": true }, "include": ["src"] }

// packages/app/tsconfig.json
{
  "compilerOptions": { "composite": true },
  "references": [{ "path": "../shared" }]
}
// Build with: tsc --build packages/app
Real-world example Structuring a large monorepo so changing one package doesn't force a full, slow rebuild of every unrelated package.

Common follow-ups: What must 'composite': true additionally require regarding the 'declaration' compiler option?

Modules

How does 'skipLibCheck' affect compilation speed and type safety, and what's the trade-off of enabling it?

Advanced
skipLibCheck skips type-checking of ALL .d.ts declaration files (including your own dependencies' bundled types), significantly speeding up compilation — especially in large projects with many dependencies — at the cost of not catching legitimate type errors or conflicts that might exist WITHIN those declaration files themselves (which is rare, but does occasionally happen with buggy or conflicting @types packages).
// tsconfig.json
{
  "compilerOptions": { "skipLibCheck": true } // commonly recommended default for faster builds
}
Real-world example Significantly speeding up compilation in a large project with many node_modules dependencies, at minimal practical risk.

Common follow-ups: Why is skipLibCheck considered a safe and commonly recommended default despite technically skipping some checking?

tsconfig & Compiler Options

How do 'extends' in tsconfig.json enable sharing a base configuration across multiple projects in a monorepo, and how do overrides get merged?

Advanced
'extends' points to a base tsconfig file whose compilerOptions are inherited as defaults; the extending file's own compilerOptions are then MERGED on top, with the extending file's values taking precedence for any overlapping keys — letting a monorepo maintain one shared 'tsconfig.base.json' while individual packages only specify their differences.
// tsconfig.base.json
{ "compilerOptions": { "strict": true, "target": "ES2020" } }

// packages/app/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": { "outDir": "./dist" } // adds to, doesn't replace, the base config
}
Real-world example Maintaining consistent strictness and target settings across dozens of packages in a monorepo from one shared base file.

Common follow-ups: Do array-valued options like 'include'/'exclude' MERGE across extends, or does the extending file's array fully REPLACE the base's?

tsconfig & Compiler Options

How does 'incremental' compilation (and the '.tsbuildinfo' file it generates) speed up repeated 'tsc' invocations during local development?

Advanced
With 'incremental': true, TypeScript writes a .tsbuildinfo file caching information about the previous compilation; on subsequent runs, it uses this cache to determine which files actually need to be reprocessed based on what changed, rather than re-type-checking the entire project from scratch every single time — dramatically speeding up repeated compiles in watch mode or CI.
// tsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.cache/tsbuildinfo"
  }
}
Real-world example Speeding up a large project's repeated local 'tsc --watch' compilations, or caching build state between CI pipeline runs.

Common follow-ups: Should the generated .tsbuildinfo file typically be committed to version control, or added to .gitignore?

Modules

How would you configure tsconfig.json differently for a library meant to be published to npm versus an internal application, particularly regarding 'declaration' and 'sourceMap'?

Advanced
A published library should set 'declaration': true (so consumers get type information) and typically 'declarationMap': true plus 'sourceMap': true (so consumers' editors can jump to real source and debug through it); an internal application typically doesn't need 'declaration' output at all, since nothing downstream consumes it as a typed dependency — reducing unnecessary build output.
// Library tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "dist"
  }
}
// Internal app tsconfig.json typically omits 'declaration' entirely
Real-world example Configuring a monorepo where shared internal packages generate full declaration output for consumption by other packages, while the top-level app doesn't.

Common follow-ups: Why would generating unnecessary .d.ts files for a non-published internal application be considered pure build overhead?

tsconfig & Compiler Options