Skip to content

Tooling and Configuration

Every TypeScript project is governed by a tsconfig.json file at the project root. This file Specifies compiler options, file inclusion/exclusion, and project references. A minimal Configuration:

{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"outDir": "./dist",
"declaration": true,
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
OptionDescriptionRecommended
targetECMAScript version for emitted JavaScript"ES2022" or "ESNext"
libLibrary files to include in compilationAuto-inferred from target
jsxJSX compilation mode"react-jsx" (React 17+)
jsxFactoryJSX factory function"React.createElement" (legacy)
jsxFragmentFactoryJSX fragment factory"React.Fragment" (legacy)
OptionDescriptionRecommended
moduleModule system for emitted code"ESNext"
moduleResolutionModule resolution strategy"bundler" or "node16"
baseUrlBase directory for non-relative module resolution"."
pathsPath aliases for module resolution{"@/*": ["src/*"]}
rootDirsMultiple root directories treated as one["src", "generated"]
resolveJsonModuleAllow importing .json filestrue
isolatedModulesEnsure each file can be transpiled independentlytrue
OptionDescriptionRecommended
strictEnable all strict type-checking optionstrue
noImplicitAnyError on implicit any typetrue (implied by strict)
strictNullChecksStrict null and undefined checkingtrue (implied by strict)
strictFunctionTypesContravariant function parameter checkingtrue (implied by strict)
strictBindCallApplyStrict checking for bind``call``applytrue (implied by strict)
strictPropertyInitializationCheck class property initialisationtrue (implied by strict)
noImplicitThisError when this gets any typetrue (implied by strict)
noImplicitReturnsError on code paths that do not return a valuetrue
noUnusedLocalsError on unused local variablestrue
noUnusedParametersError on unused function parameterstrue
noFallthroughCasesInSwitchError on switch case fallthroughtrue
exactOptionalPropertyTypesDistinguish between optional and undefined-present propertiestrue
OptionDescriptionRecommended
outDirDirectory for emitted JavaScript"./dist"
declarationGenerate .d.ts filestrue for libraries
declarationMapGenerate sourcemaps for .d.ts filestrue for libraries
sourceMapGenerate sourcemapstrue
noEmitType-check without emitting filestrue for type-checking only

strict: true enables all of the following flags simultaneously:

  • noImplicitAny
  • noImplicitThis
  • alwaysStrict
  • strictBindCallApply
  • strictNullChecks
  • strictFunctionTypes
  • strictPropertyInitialization

Common Pitfall: Enabling strict in an existing JavaScript codebase will produce many errors. Migrate incrementally by enabling individual strict flags one at a time, starting with strictNullChecks.

When exactOptionalPropertyTypes is enabled, TypeScript distinguishes between a property that is Absent and a property that is present with the value undefined:

interface Config {
host?: string;
}
const a: Config = {};
const b: Config = { host: undefined };

Under exactOptionalPropertyTypesThe second line is an error. To explicitly allow undefined Declare the property as host?: string | undefined.

The original TypeScript resolution strategy. Resolves relative to the importing file. Rarely used in Modern projects.

Mimics Node.js CommonJS resolution. Searches node_modules hierarchically and checks package.json types/typings fields.

Node.js ESM-aware resolution (TypeScript 4.7+). Respects the "type": "module" field in package.json and enforces extension-including imports for ES modules.

Designed for projects using bundlers (Vite, webpack, esbuild) that handle module resolution Themselves (TypeScript 5.0+). Does not enforce Node.js-specific resolution rules.

Featurenodenode16bundler
ESM-awareNoYesNo
Requires file extensions in ESMNoYesNo
Checks exports in package.jsonNoYesYes
Best forLegacy projectsNode.js native ESMBundled web apps
Terminal window
npm install --save-dev @types/node
npm install --save-dev @types/react
npm install --save-dev @types/express

When TypeScript encounters an import from a module without corresponding .ts or .d.ts files, it Searches node_modules/@types/ for a matching package. The search order is controlled by typeRoots (default: node_modules/@types/).

{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./custom-types"],
"types": ["node", "jest"]
}
}

When types is specified, only the listed packages from typeRoots are included. An empty array ("types": []) disables automatic inclusion of all @types packages.

node_modules/
@types/
node/
index.d.ts
package.json
react/
index.d.ts
package.json

When a library lacks type definitions, you can:

  1. Create a declaration file locally: declarations/my-lib.d.ts.
  2. Submit a pull request to DefinitelyTyped.
  3. Use declare module "my-lib" {} as a temporary ambient declaration.

Type-checks the project without producing output files:

Terminal window
tsc --noEmit

This is the recommended command for CI pipelines that only need to verify type correctness.

Enables all strict type-checking options regardless of tsconfig.json settings:

Terminal window
tsc --strict --noEmit

Watches for file changes and re-type-checks incrementally:

Terminal window
tsc --watch

TypeScript supports incremental compilation via the tsconfig.json flag or --incremental CLI Option. The compiler stores dependency information in a .tsbuildinfo file, enabling faster Subsequent compilations:

{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo"
}
}

ts-node executes TypeScript files directly without a separate compilation step:

Terminal window
npx ts-node src/index.ts

Configuration for ts-node in tsconfig.json:

{
"ts-node": {
"transpileOnly": true,
"files": true
}
}

transpileOnly: true skips type checking for faster execution (useful during development). Remove It for production scripts where type safety is critical.

Common Pitfall: ts-node with ESM requires additional configuration. Set "esm": true in the ts-node section of tsconfig.json and ensure package.json has "type": "module".

Terminal window
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/strict-boolean-expressions": "warn"
}
}

| Rule | Description | | -------------------------------------------------- | -------------------------------- | --- | --- | | @typescript-eslint/no-explicit-any | Disallow any type | | @typescript-eslint/no-unused-vars | Disallow unused variables | | @typescript-eslint/explicit-function-return-type | Require explicit return types | | @typescript-eslint/no-floating-promises | Require promises to be handled | | @typescript-eslint/strict-boolean-expressions | Restrict boolean expressions | | @typescript-eslint/prefer-nullish-coalescing | Prefer ?? over | | | | @typescript-eslint/prefer-optional-chain | Prefer ?. over manual checks | | @typescript-eslint/no-non-null-assertion | Disallow ! non-null assertions |

Common Pitfall: The @typescript-eslint/recommended-requiring-type-checking config requires parserOptions.project to point to a valid tsconfig.json. This makes linting slower because it Runs the type checker. Use this config in CI, not in the editor.

Project references allow a TypeScript project to be split into multiple sub-projects, each with its Own tsconfig.json. This is essential for monorepo architectures.

packages/
core/
tsconfig.json
src/
utils/
tsconfig.json
src/
tsconfig.json

Root tsconfig.json:

{
"files": [],
"references": [{ "path": "./packages/core" }, { "path": "./packages/utils" }]
}

Sub-project tsconfig.json:

{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true
},
"include": ["src"]
}
  • composite: true must be set in each referenced project.
  • declaration: true must be enabled so other projects can reference the types.
  • Referenced projects must list all their input files explicitly or via include (no files globbing with **).
  • References form a directed acyclic graph (no circular references).
Terminal window
tsc --build

This command builds all referenced projects in dependency order, incrementally. It uses the .tsbuildinfo files to avoid redundant work.

import { something } from "@myorg/core';

The path alias is resolved via paths in tsconfig.json or through Node.js module resolution.

Declaration files describe the shape of JavaScript code without providing implementations:

declare module 'my-library' {
export interface Options {
debug: boolean;
timeout: number;
}
export function createClient(options: Options): Client;
export interface Client {
connect(): Promise<void>;
disconnect(): Promise<void>;
send(message: string): Promise<string>;
}
}
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development'' | "production' | 'test';
API_URL: string;
}
}
interface Window {
analytics: {
track(event: string, data?: Record<string, unknown>): void;
};
}
}
export {};

The export {} at the end turns the file into a module, which is required for declare global to Work.

For untyped modules:

declare module 'untyped-module' {
export function doSomething(input: string): number;
}

For modules that export a default:

declare module 'another-untyped' {
const value: {
method(): void;
};
export default value;
}
ValueOutput
"preserve"Emit .jsx files (for further processing by Babel)
"react"React.createElement
"react-native"React.createElement (preserves JSX as React.NativeComponent)
"react-jsx"_jsx from react/jsx-runtime (React 17+)
"react-jsxdev"_jsxDev from react/jsx-dev-runtime (React 17+ dev)
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}

This configuration uses h instead of React.createElementWhich is the convention for Preact.

The satisfies operator validates that an expression matches a type without widening the Expression’s type:

const config = {
host: "localhost'',
port: 3000,
debug: false,
} satisfies Record<string, string | number | boolean>;
const host: string = config.host;
const port: number = config.port;
const debug: boolean = config.debug;

Without satisfiesAssigning to Record<string, string | number | boolean> would widen the Property types. With satisfiesThe literal types are preserved while still validating against the Broader type.

const a: Record<string, string | number> = {
key: "value',
};
const b = {
key: "value'',
} satisfies Record<string, string | number>;
type A = (typeof a)["key'];
type B = (typeof b)['key'];

A is string | number (widened by the annotation). B is "value" (literal type preserved by satisfies).

TypeScript 5.0+ supports const type parameters, which infer the narrowest (literal) type for Generic type arguments:

function createRoute<const T extends string[]>(path: `/${string}`, params: T): void {}
createRoute('/users/:id', ['id'] as const);

Without const on the type parameter, T would be inferred as string[]. With const``T is Inferred as readonly ["id"].

function logged<T extends { new (...args: any[]): {} }>(target: T, context: ClassDecoratorContext) {
console.log(`Decorating ${String(context.name)}`);
}

using and Symbol.dispose (TypeScript 5.2+)

Section titled “using and Symbol.dispose (TypeScript 5.2+)”
class Resource implements Disposable {
dispose(): void {
console.log('Resource cleaned up');
}
}
function example(): void {
using resource = new Resource();
}

The using keyword ensures that dispose() is called when the variable goes out of scope, even if An exception is thrown.

include specifies file patterns, while files specifies individual files. If both are present, Both are used. Do not use both include and files in the same tsconfig.json unless you have a Specific reason.

Pitfall 2: Path Aliases Not Resolved at Runtime

Section titled “Pitfall 2: Path Aliases Not Resolved at Runtime”

Path aliases in tsconfig.json (paths) are resolved only by the TypeScript compiler. The bundler Or runtime must be configured separately to resolve these aliases. Without matching bundler Configuration, imports will fail at runtime.

Pitfall 3: composite Requires Explicit File Listing

Section titled “Pitfall 3: composite Requires Explicit File Listing”

When composite: true is set, all source files must be listed explicitly via include or files. Wildcard patterns like "src/**/*.ts" are supported, but the compiler must be able to determine the Full list of input files without filesystem traversal.

Pitfall 4: Declaration Files and isolatedModules

Section titled “Pitfall 4: Declaration Files and isolatedModules”

When isolatedModules is enabled, declaration files must be careful about re-exports. Re-exporting Types and values from the same module can cause issues with some bundlers.

This topic covers the core concepts of tooling and configuration, including underlying theory, practical implementation, and key applications.

Key concepts include:

  • type annotations and interfaces
  • generics and utility types
  • async/await and Promises
  • modules and namespaces
  • type guards and narrowing

Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.

Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.

The TypeScript toolchain revolves around tsconfig.json, which controls compilation, type checking, and project structure. The strict flag enables all safety checks simultaneously, though incremental migration via individual flags is recommended for existing codebases. Module resolution strategies (node, node16, bundler) determine how imports are found. Project references enable monorepo builds with incremental compilation, while declaration files (.d.ts) bridge untyped JavaScript libraries into the type system.

  • [[typescript/typescript]] - Overview of the TypeScript ecosystem
  • [[typescript/enums-and-modules]] - Module resolution and import patterns
  • [[typescript/types-and-annotations]] - Type system fundamentals
  • [[typescript/advanced-types]] - Declaration file patterns and module augmentation