Episode 1 — Fundamentals / 1.25 — TypeScript Essentials

1.25.j — tsconfig.json Explained

In one sentence: tsconfig.json is the configuration file for the TypeScript compiler — it controls what JavaScript version to target, which module system to use, how strict the type checking is, and which files to include or exclude.

Navigation: ← 1.25 Overview · 1.25.k — TypeScript Compiler →


1. What tsconfig.json does

tsconfig.json sits at the root of your TypeScript project and tells the compiler (tsc):

  • What to compile — which files to include/exclude
  • How to compile — target JavaScript version, module system, output directory
  • How strict to be — which type-checking rules to enforce
  • Where to put output — compiled JavaScript destination

Without tsconfig.json, you must pass all options as CLI flags. With it, just run npx tsc.


2. Creating tsconfig.json

npx tsc --init

This generates a tsconfig.json with many options commented out and sensible defaults. The generated file includes explanatory comments for each option.


3. Key compiler options explained

target — JavaScript output version

Determines which JS features are available in the output:

{
  "compilerOptions": {
    "target": "ES2020"
  }
}
ValueOutput includesUse when
"ES5"No arrow functions, no let/const, no template literalsSupporting very old browsers
"ES2015" / "ES6"Arrow functions, let/const, classes, promisesBroad browser support
"ES2020"Optional chaining (?.), nullish coalescing (??), BigIntRecommended for most projects
"ES2022"Top-level await, Array.at(), class fieldsModern environments
"ESNext"Latest features — changes with each TS releaseWhen using a bundler that handles downleveling

Rule of thumb: Match your target to your deployment environment's minimum supported JavaScript version.

module — module system

Controls how import/export statements are compiled:

{
  "compilerOptions": {
    "module": "commonjs"
  }
}
ValueOutput styleUse when
"commonjs"require() / module.exportsNode.js (traditional)
"ESNext" / "ES2020"import / export (kept as-is)Bundlers (Vite, webpack, Rollup)
"NodeNext"Smart — CommonJS or ESM based on .mts/.cts extensionNode.js with ESM support

strict — enable all strict checks

{
  "compilerOptions": {
    "strict": true
  }
}

strict: true enables ALL of these:

FlagWhat it does
strictNullChecksnull/undefined not assignable to other types
noImplicitAnyError when TypeScript cannot infer a type (falls to any)
strictFunctionTypesStricter function parameter checking
strictBindCallApplyCheck bind, call, apply argument types
strictPropertyInitializationClass properties must be initialized
noImplicitThisError when this has implicit any type
alwaysStrictEmits "use strict" in every file
useUnknownInCatchVariablescatch(e)e is unknown instead of any

Always use strict: true for new projects. It catches the most bugs.

outDir — compiled output directory

{
  "compilerOptions": {
    "outDir": "./dist"
  }
}

Compiled .js files go into ./dist/ instead of alongside source .ts files.

rootDir — source root directory

{
  "compilerOptions": {
    "rootDir": "./src"
  }
}

Tells the compiler where your source files live. This affects the output directory structure:

src/index.ts    → dist/index.js      (not dist/src/index.js)
src/utils/a.ts  → dist/utils/a.js

lib — built-in type declarations

Controls which built-in API types are available:

{
  "compilerOptions": {
    "lib": ["ES2020", "DOM", "DOM.Iterable"]
  }
}
ValueIncludes types for
"ES2020"Promise, Map, Set, Array.from, etc.
"DOM"document, window, HTMLElement, fetch, etc.
"DOM.Iterable"NodeList.forEach, iterable DOM collections
"WebWorker"Web Worker APIs

For Node.js projects (no browser): omit "DOM" and install @types/node instead.

jsx — React JSX handling

{
  "compilerOptions": {
    "jsx": "react-jsx"
  }
}
ValueBehavior
"react-jsx"Modern React 17+ automatic JSX transform (no import React needed)
"react"Classic: React.createElement (requires import React)
"preserve"Keep JSX as-is — let the bundler handle it

moduleResolution — how imports are resolved

{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}
ValueUse when
"node"Classic Node.js resolution (node_modules lookup)
"node16" / "nodenext"Modern Node.js with ESM support
"bundler"Using Vite, webpack, esbuild (recommended for bundled projects)

esModuleInterop — import compatibility

{
  "compilerOptions": {
    "esModuleInterop": true
  }
}

Enables import express from 'express' instead of import * as express from 'express'. Almost always set to true.

skipLibCheck — faster compilation

{
  "compilerOptions": {
    "skipLibCheck": true
  }
}

Skips type-checking of .d.ts declaration files (from node_modules/@types). Speeds up compilation significantly. Recommended: true — your own code is still checked.

include / exclude — file selection

{
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}
PropertyPurpose
includeGlob patterns for files to compile
excludeGlob patterns for files to skip
filesExplicit list of files (rarely used)

4. Common configurations

Minimal configuration

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Node.js project

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

React project (Vite)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "strict": true,
    "jsx": "react-jsx",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

Key differences for React:

  • "jsx": "react-jsx" — JSX support
  • "noEmit": true — Vite handles compilation; tsc only type-checks
  • "moduleResolution": "bundler" — let Vite resolve imports
  • "lib" includes "DOM" — browser APIs needed

Next.js project

{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Library (publishing to npm)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020"],
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationDir": "./dist/types",
    "sourceMap": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}

Key for libraries:

  • "declaration": true — generates .d.ts files for consumers
  • "sourceMap": true — enables debugging in consuming projects

5. extends — inheriting from a base config

Share common settings across multiple projects or configurations:

// tsconfig.base.json
{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

// tsconfig.json (inherits from base)
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}

You can also extend community-maintained configs:

npm install -D @tsconfig/node20
{
  "extends": "@tsconfig/node20/tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

Available community configs: @tsconfig/node20, @tsconfig/strictest, @tsconfig/recommended.


6. Path aliases

Map import paths to directories:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@utils/*": ["./src/utils/*"]
    }
  }
}
// Instead of:
import { Button } from "../../../components/Button";

// Use:
import { Button } from "@components/Button";

Note: Path aliases in tsconfig.json only affect TypeScript. Your bundler (Vite, webpack) may need separate alias configuration.


7. Complete options reference table

OptionTypeDefaultPurpose
targetstring"ES3"JS output version
modulestringdepends on targetModule system
libstring[]depends on targetBuilt-in API types
strictbooleanfalseAll strict checks
outDirstring(same as source)Output directory
rootDirstring(inferred)Source directory
jsxstring-JSX handling mode
moduleResolutionstring"node"Import resolution strategy
esModuleInteropbooleanfalseDefault import compatibility
skipLibCheckbooleanfalseSkip .d.ts checking
declarationbooleanfalseGenerate .d.ts files
sourceMapbooleanfalseGenerate .js.map files
noEmitbooleanfalseType-check only, no output
resolveJsonModulebooleanfalseAllow importing .json files
isolatedModulesbooleanfalseEnsure each file can be compiled independently
allowJsbooleanfalseAllow .js files in project
checkJsbooleanfalseType-check .js files
incrementalbooleanfalseCache for faster rebuilds
forceConsistentCasingInFileNamesbooleanfalseEnforce case-sensitive imports

Key takeaways

  1. tsconfig.json configures the TypeScript compiler — create it with npx tsc --init.
  2. strict: true enables all strict checks — always recommended for new projects.
  3. target controls the JS output version; module controls the module system.
  4. Use noEmit: true when a bundler (Vite, Next.js) handles compilation.
  5. include/exclude control which files are compiled.
  6. Use extends to share configuration across projects or inherit from community configs.
  7. Match your tsconfig to your environment — Node.js, React, Next.js, and libraries each need different settings.

Explain-It Challenge

Explain without notes:

  1. What does strict: true enable, and why is it recommended?
  2. When would you set noEmit: true in tsconfig?
  3. What is the difference between target and module options?

Navigation: ← 1.25 Overview · 1.25.k — TypeScript Compiler →