⚡IntermediateTypeScript in Practice

tsconfig.json Deep Dive

Master the most important TypeScript compiler options - strict mode flags, module settings, path aliases, and project references.

Overview

`tsconfig.json` is the control center of a TypeScript project. Beyond `"strict": true`, dozens of options shape how TypeScript compiles, what it allows, and what it catches. **Strict mode** is actually a shorthand for multiple individual flags: `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`, and others. You can enable them individually if you need to migrate an existing JS project gradually. **Module settings** (`"module"`, `"moduleResolution"`, `"esModuleInterop"`) control how TypeScript handles `import`/`export` and how it resolves module paths - crucial for Node.js, bundlers (Vite/webpack), and ESM environments. **Path aliases** (`"paths"`) let you write `import { Button } from "@/components/Button"` instead of `"../../components/Button"`. **Project references** (`"references"`) split a large codebase into sub-projects for faster incremental builds.

Key Points

  • •`"strict": true` enables 7+ strict flags at once - always use it for new projects
  • •`"noUncheckedIndexedAccess"` makes index signatures return `T | undefined` (not included in strict)
  • •`"noImplicitReturns"` errors when a function branch forgets to return a value
  • •`"exactOptionalPropertyTypes"` distinguishes `{ x?: string }` (absent) from `{ x: string | undefined }`
  • •`"target"` controls what JS syntax TypeScript emits: ES5, ES2020, ESNext, etc.
  • •`"module"` controls the import/export format: CommonJS, ESM, NodeNext, etc.
  • •`"paths"` defines import path aliases: `"@/*": ["./src/*"]`
  • •`"incremental"` + `"tsBuildInfoFile"` caches build info for faster recompilation

Examples

Recommended tsconfig for a modern project

// tsconfig.json
{
  "compilerOptions": {
    // Language / Type Checking
    "target": "ES2022",
    "lib": ["ES2022", "DOM"],
    "strict": true,                       // Enables all strict checks
    "noUncheckedIndexedAccess": true,     // index[key] is T | undefined
    "noImplicitReturns": true,            // All code paths must return
    "noImplicitOverride": true,           // Must use override keyword
    "exactOptionalPropertyTypes": true,   // ? vs | undefined distinction
    "noPropertyAccessFromIndexSignature": true,

    // Module
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,

    // Output
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,                  // Emit .d.ts files
    "declarationMap": true,               // Source maps for .d.ts
    "sourceMap": true,

    // Path aliases
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@utils/*": ["./src/utils/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Understanding strict mode flags individually

// Each of these is enabled by "strict": true

// 1. noImplicitAny  -  must annotate parameters
// function greet(name) {} // Error without annotation
function greet(name: string) { return `Hello ${name}`; }

// 2. strictNullChecks  -  null and undefined are not assignable to other types
let x: string;
// x = null; // Error

// 3. strictFunctionTypes  -  function parameters are checked contravariantly
type F = (value: string) => void;
// const f: F = (value: string | number) => {}; // Error (stricter parameter)

// 4. strictPropertyInitialization  -  class properties must be initialized
class Service {
  url: string; // Error without initializer or constructor assignment

  // Fix 1: initialize inline
  // url: string = '';

  // Fix 2: initialize in constructor
  constructor(url: string) {
    this.url = url;
  }
}

// 5. noImplicitThis  -  'this' in functions must be explicitly typed
function getLabel(this: { label: string }): string {
  return this.label; // OK  -  this is typed
}

const obj = { label: 'Hello', getLabel };
console.log(obj.getLabel()); // "Hello"

Path aliases and project references

// tsconfig.json with path aliases
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*":            ["./src/*"],
      "@components/*":  ["./src/components/*"],
      "@hooks/*":       ["./src/hooks/*"],
      "@types/*":       ["./src/types/*"]
    }
  }
}

// Without aliases (relative hell):
// import { Button } from '../../../components/ui/Button';
// import { useAuth } from '../../hooks/useAuth';

// With aliases (clean and refactor-proof):
// import { Button } from '@components/ui/Button';
// import { useAuth } from '@hooks/useAuth';

// IMPORTANT: tsconfig paths are TypeScript-only
// Your bundler (Vite, webpack, Next.js) also needs to know about them:

// vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
});

Common Mistakes

  • `"strict": true` does NOT include `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, or `noImplicitReturns` - add these explicitly for maximum safety
  • Setting `"module": "CommonJS"` with `"moduleResolution": "NodeNext"` - these must be compatible; NodeNext requires `"module": "NodeNext"` or `"Node16"`
  • Adding path aliases to tsconfig without configuring the same aliases in your bundler - TypeScript resolves the types, but the bundler (Vite, webpack) handles runtime resolution

Note

Start with `"strict": true`, then add `"noUncheckedIndexedAccess": true` and `"noImplicitReturns": true` separately - these three options catch the majority of real TypeScript bugs at compile time. The extra strictness is worth the upfront annotation effort.

Exercises

Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.

Loading editor…

Completed: 0 / 2 exercises

Knowledge Check

Question 1 of 4

Which strict flag makes TypeScript error when a function parameter has no type annotation?