| |

TypeScript 3 ๐Ÿ”ท The TypeScript CLI and tsconfig.json

The TypeScript CLI (tsc) is the program that reads your .ts files, type-checks them, and emits .js. But tsc on its own does almost nothing useful โ€” it needs configuration. That configuration lives in tsconfig.json, a single file at the project root that tells the compiler which files to include, what JavaScript version to target, how strict to be, and where to write the output. Learning tsc and tsconfig.json together is unavoidable โ€” every TypeScript project has both, and understanding them is what turns “I write .ts and hope for the best” into “I control exactly what the compiler does.”

Key point: tsconfig.json is not optional. Without it, tsc treats every file you pass as an isolated compile and applies defaults you didn’t choose. With it, the compiler knows your project โ€” its files, its target, its strictness. Every real TypeScript project has a tsconfig.json, and every serious TypeScript developer reads it.


The CLI โ€” what tsc does

tsc is the TypeScript compiler. Invoked with no arguments in a directory that has a tsconfig.json, it reads the config and compiles the project. Invoked with file paths, it compiles those files with default settings.

tsc                    # Compile per tsconfig.json
tsc file.ts            # Compile a single file
tsc --init             # Generate a starter tsconfig.json
tsc --watch            # Rebuild on change
tsc --noEmit           # Type-check only, no output
tsc --project path/    # Use a specific tsconfig
tsc --build tsconfig.json  # Build a project (references)
tsc --help             # Show all flags

Two modes:

ModeTriggerBehavior
Project modetsc (no args, tsconfig.json present)Compile everything the config includes
File modetsc file.tsCompile one file with CLI flags only

Project mode is what you use 99% of the time. File mode is fine for one-off checks but doesn’t scale โ€” you’d have to list every file and every flag on the command line.

Exit codes matter in CI:

  • 0 โ€” no errors, compilation succeeded
  • 1 โ€” errors found
  • 2 โ€” configuration error (bad tsconfig, missing file)

That’s what makes tsc --noEmit a valid CI step โ€” a non-zero exit code fails the build.

Why the distinction between project and file mode: File mode ignores tsconfig.json entirely. If you run tsc index.ts in a project that has strict mode enabled, you get the default (loose) settings instead. That’s a common source of confusion โ€” the editor says one thing, tsc index.ts says another. Use tsc without file arguments when you want the project’s settings.


The tsconfig.json file

tsconfig.json is a JSON file (with comments allowed โ€” it’s parsed as JSONC) that configures the compiler. Its presence in a directory marks that directory as the project root. It has a few top-level keys:

KeyPurpose
compilerOptionsHow the compiler behaves
includeGlob patterns for files to include
excludeGlobs to exclude
filesExplicit list of files (rare)
extendsInherit from another config
referencesProject references (monorepos)

Most projects use compilerOptions plus include (and sometimes exclude). The other keys are for specific scenarios.

A minimal, modern config:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

What each line does:

  • target โ€” the JavaScript version to emit
  • module โ€” the module system for the output
  • moduleResolution โ€” how imports are resolved
  • strict โ€” turns on all strict checks
  • esModuleInterop โ€” smooth interop between CommonJS and ESM
  • skipLibCheck โ€” skip type-checking of .d.ts files (faster builds)
  • forceConsistentCasingInFileNames โ€” reject imports that differ only in case (important on macOS/Windows)
  • outDir / rootDir โ€” where output goes, where source lives
  • include โ€” files to compile

tsc --init generates a config with most of these commented out and explained. It’s worth reading through once.

Why JSON with comments: TypeScript’s team wanted tsconfig.json to be self-documenting. tsc --init writes a file where every option has a // explanation next to it. That’s not standard JSON, but tsc accepts it (and most editors highlight it correctly).


compilerOptions โ€” the ones that matter

There are over 100 compiler options. Most projects use about fifteen. Here are the ones that actually change how you write code.

target โ€” output JavaScript version

TargetEmitsUse when
ES5Old syntax, polyfilled helpersLegacy browsers
ES2017Modern async/awaitNode 8+
ES2020Optional chaining, nullishNode 14+
ES2022Top-level await, class fieldsNode 16+, modern browsers
ESNextLatest supportedNew projects

target decides which syntax the compiler downlevels. If you target ES5, async/await becomes a state machine. If you target ES2022, it stays as async/await.

module โ€” output module system

ModuleUse when
CommonJSNode with require()
ESNextBundlers (Vite, webpack)
NodeNextModern Node with ESM
Node16Node 16+ with mixed CJS/ESM

module decides what import/export becomes in the emitted .js. For Node projects, NodeNext is the modern choice. For bundlers, ESNext.

strict โ€” the master switch

"strict": true

Turning this on enables all of these:

  • strictNullChecks โ€” null and undefined are distinct from other types
  • strictFunctionTypes โ€” contravariant function parameter checks
  • strictBindCallApply โ€” bind/call/apply are typed
  • strictPropertyInitialization โ€” class properties must be initialized
  • noImplicitAny โ€” reject implicit any
  • noImplicitThis โ€” reject implicit this
  • useUnknownInCatchVariables โ€” catch variables are unknown, not any

Every new project should start with strict: true. It’s the single option that most improves code quality.

What it costs: some existing code won’t compile without changes. Implicit anys need annotations. Null checks need ?. or guards. That’s the point โ€” the compiler makes you handle cases you were silently ignoring.

esModuleInterop

"esModuleInterop": true

Without this, importing a CommonJS module (like express) requires import * as express from 'express'. With it, import express from 'express' works. Turn it on. The allowSyntheticDefaultImports companion is included automatically.

skipLibCheck

"skipLibCheck": true

Skips type-checking of declaration files in node_modules. Dramatically faster, and in practice, library .d.ts files are already correct enough. Turn it on.

forceConsistentCasingInFileNames

"forceConsistentCasingInFileNames": true

Rejects imports that differ from the file’s real name only in case. Matters on macOS and Windows where the filesystem is case-insensitive โ€” otherwise Linux CI will fail with “file not found” for code that “worked on your machine.”

outDir and rootDir

"outDir": "dist",
"rootDir": "src"

rootDir is the source root. outDir is where .js goes. When both are set, tsc mirrors the source tree inside outDir โ€” src/a/b.ts becomes dist/a/b.js.

Without them, tsc writes .js next to .ts โ€” messy for real projects.

Why so few options matter: The default tsc --init generates a file with dozens of options, most commented out. The active ones do most of the work. Learn these, and you understand 95% of TypeScript configs you’ll encounter.


include, exclude, files

These control which files the compiler sees.

include โ€” glob patterns:

"include": ["src/**/*.ts", "src/**/*.tsx"]

** matches any number of directories; * matches any name within a level. Common patterns:

PatternMatches
src/**/*.tsAll .ts files under src/
src/**/*Everything under src/
**/*.tsAll .ts everywhere except excluded
src/*.ts.ts directly in src/ (not subdirs)

exclude โ€” patterns to remove:

"exclude": ["node_modules", "dist", "**/*.spec.ts"]

By default, node_modules, bower_components, jspm_packages, and outDir are excluded. Adding to exclude removes more.

files โ€” an explicit list:

"files": ["src/index.ts", "src/cli.ts"]

files is rarely used. It bypasses globbing and compiles exactly those files. Useful for small scripts or specific build targets.

Interaction: include and exclude work together. files overrides both โ€” if you list files explicitly, they’re included regardless of exclude. Files that include picks up but exclude removes are dropped.

A note on exclude and include paths: All paths in these fields are relative to the tsconfig.json file’s directory. Patterns without leading ./ are globs; with ./ they’re literal.

Why globs and not file lists: A project with hundreds of files would need constant tsconfig.json updates. Globs let the config describe “everything under src/” once. New files are picked up automatically.


Inheritance with extends

extends lets one config inherit from another. Values are merged, with the child overriding the parent.

// tsconfig.base.json
{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "ESNext",
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
// tsconfig.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

The child inherits strict, target, module, etc., and adds outDir/rootDir. This is how monorepos and framework scaffolds share config.

Common uses:

  • Framework scaffolds โ€” @tsconfig/node20, @tsconfig/vite-react, @tsconfig/strictest
  • Monorepos โ€” a base config for all packages, each with its own overrides
  • Multi-target projects โ€” a server config and a client config extending a shared base

Rules to know:

  • extends is resolved relative to the file doing the extending
  • compilerOptions merges โ€” the child wins on conflicts
  • files, include, exclude are not merged โ€” the child’s values replace the parent’s
  • Paths in the parent are resolved relative to the parent, not the child

Why inheritance was added: Framework authors wanted to ship “the recommended config for TypeScript on Node 20” as a package. Without extends, every project copied the same 20 lines. With extends, they reference a package and get updates.


Project references

For large codebases and monorepos, references lets one tsconfig.json depend on another. tsc --build then compiles them in dependency order and uses incremental builds.

// tsconfig.json (root)
{
  "references": [
    { "path": "./packages/shared" },
    { "path": "./packages/api" }
  ],
  "files": []
}

Each referenced project has its own tsconfig.json with composite: true.

// packages/shared/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

What composite: true enables:

  • declaration is forced on โ€” .d.ts files are emitted
  • incremental is forced on โ€” .tsbuildinfo caches between builds
  • The project can be referenced by others

Building:

tsc --build               # Build all projects in dependency order
tsc --build --watch       # Watch and rebuild changed projects only
tsc --build --clean       # Remove build output

Why use it: In a monorepo, rebuilding everything on every change is slow. Project references let tsc rebuild only what changed, in the right order. That’s the mechanism behind fast monorepo builds in Nx, Turborepo, and similar tools.

Why composite and references exist: TypeScript was designed for single projects. As monorepos grew, the compiler needed to understand a project’s dependencies and only rebuild what changed. Project references are that mechanism โ€” and the foundation of incremental builds.


A worked example

A small Node project compiled to dist/.

package.json:

{
  "name": "ts-cli-demo",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "watch": "tsc --watch",
    "typecheck": "tsc --noEmit",
    "start": "node dist/index.js"
  },
  "devDependencies": {
    "typescript": "^5.4.0"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "dist",
    "rootDir": "src",
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist", "**/*.spec.ts"]
}

src/index.ts:

interface User {
  id: number;
  name: string;
}

function greet(user: User): string {
  return `Hello, ${user.name} (id ${user.id})!`;
}

const alice: User = { id: 1, name: 'Alice' };
console.log(greet(alice));

Build:

npm run build

Output:

dist/
โ”œโ”€โ”€ index.d.ts
โ”œโ”€โ”€ index.js
โ””โ”€โ”€ index.js.map

.d.ts is the type declaration for other TypeScript projects. .js is the executable. .js.map maps runtime errors back to source.

Run:

node dist/index.js
# Hello, Alice (id 1)!

Try an error: change id: 1 to id: '1'. npm run build fails with a precise error and no output is emitted. That’s the compiler doing its job.

Why declaration: true and sourceMap: true: Declaration files make your code importable from other TypeScript projects. Source maps make debugging runtime errors point back to the original .ts line. For libraries and servers, both are near-mandatory.


tsc --init โ€” the generated config

Running tsc --init creates a tsconfig.json with every option listed and commented out, and a reasonable subset enabled. It’s long. Here’s the essence:

{
  "compilerOptions": {
    "target": "ES2016",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

That’s the minimal generated config. Change target and module for your environment. Leave strict: true on.

If you’re on Node 20+ and using ESM, replace module with NodeNext and moduleResolution with NodeNext. If you’re using a bundler, use module: ESNext, moduleResolution: Bundler.

Why start with --init and not a blank file: The generated file is a checklist. Reading through it once is one of the best ways to learn what options exist. You don’t need to enable them all โ€” you need to know they’re there.


Complete Example Session

# ============================================
# PART 1: CREATE A FRESH PROJECT
# ============================================

mkdir tsconfig-demo
cd tsconfig-demo
npm init -y
npm install --save-dev typescript

# ============================================
# PART 2: GENERATE A TSCONFIG
# ============================================

npx tsc --init
# Created a new tsconfig.json with:
#   target: es2016
#   module: commonjs
#   strict: true
#   ...

# ============================================
# PART 3: EDIT THE TSCONFIG
# ============================================

cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "dist",
    "rootDir": "src",
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist", "**/*.spec.ts"]
}
EOF

# ============================================
# PART 4: WRITE SOURCE
# ============================================

mkdir src
cat > src/index.ts << 'EOF'
interface User { id: number; name: string; }

export function greet(user: User): string {
  return `Hello, ${user.name}!`;
}

const alice: User = { id: 1, name: 'Alice' };
console.log(greet(alice));
EOF

# ============================================
# PART 5: COMPILE
# ============================================

npx tsc
# (no output โ€” success)

ls dist/
# [ index.d.ts  index.js  index.js.map ]

# ============================================
# PART 6: RUN
# ============================================

node dist/index.js
# Hello, Alice!

# ============================================
# PART 7: TRIGGER AN ERROR
# ============================================

cat > src/broken.ts << 'EOF'
const x: number = 'not a number';
EOF

npx tsc
# [ src/broken.ts:1:7 - error TS2322: Type 'string' is not assignable to type 'number'. ]
# (no output emitted for broken.ts)

rm src/broken.ts

# ============================================
# PART 8: WATCH MODE
# ============================================

npx tsc --watch
# [ Watching for file changes. ]
# (edit src/index.ts in another terminal โ†’ dist/ updates)

# ============================================
# PART 9: TYPE-CHECK ONLY
# ============================================

npx tsc --noEmit
# (no output โ€” no errors)
# exit code 0

# ============================================
# PART 10: INHERIT FROM A BASE
# ============================================

cat > tsconfig.base.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2022",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
EOF

cat > tsconfig.json << 'EOF'
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "declaration": true
  },
  "include": ["src"]
}
EOF

npx tsc
# (still compiles cleanly with the inherited config)

Quick Reference

CLI Commands

CommandPurpose
tscCompile per tsconfig.json
tsc file.tsCompile a single file (ignores tsconfig)
tsc --initGenerate starter config
tsc --watchRebuild on change
tsc --noEmitType-check only
tsc --project pathUse a specific config
tsc --buildBuild a project (references)
tsc --build --cleanClean build output
tsc --helpFull flag list
tsc --versionShow compiler version

Exit Codes

CodeMeaning
0No errors
1Errors found
2Config error

Top-Level Keys

KeyPurpose
compilerOptionsCompiler behavior
includeGlobs for files to compile
excludeGlobs to exclude
filesExplicit file list (overrides include/exclude)
extendsInherit from another config
referencesProject references

Essential compilerOptions

OptionRecommended
targetES2022 or ESNext
moduleESNext, NodeNext, or CommonJS
moduleResolutionBundler or NodeNext
stricttrue
esModuleInteroptrue
skipLibChecktrue
forceConsistentCasingInFileNamestrue
outDirdist
rootDirsrc
declarationtrue for libraries
sourceMaptrue for debugging

strict Enables

Sub-optionEffect
strictNullChecksnull / undefined are distinct
strictFunctionTypesContravariant parameter checks
strictBindCallApplyTyped bind/call/apply
strictPropertyInitializationClass props must be set
noImplicitAnyReject implicit any
noImplicitThisReject implicit this
useUnknownInCatchVariablescatch is unknown

Common Glob Patterns

PatternMatches
src/**/*.tsAll .ts under src/
src/**/*Everything under src/
**/*.tsAll .ts anywhere (minus exclude)
src/*.ts.ts in src/ only
!src/**/*.spec.tsNegation in files arrays

extends Behavior

KeyMerged?
compilerOptionsโœ… child wins
includeโŒ child replaces
excludeโŒ child replaces
filesโŒ child replaces
referencesโŒ child replaces

Project References

OptionPurpose
composite: trueEnable referencing
declaration: trueEmit .d.ts (auto with composite)
incremental: trueCache builds (auto with composite)
tsc --buildBuild in dependency order
tsc --build --watchIncremental watch

Best Practices

โœ… Do This:

// Enable strict mode
"strict": true                                         // โœ…

// Modern target
"target": "ES2022"                                     // โœ…

// Separate source and output
"rootDir": "src", "outDir": "dist"                     // โœ…

// Skip lib check for speed
"skipLibCheck": true                                   // โœ…

// Ensure consistent case
"forceConsistentCasingInFileNames": true               // โœ…

// Emit declarations for libraries
"declaration": true                                    // โœ…

// Use project references for monorepos
"references": [{ "path": "./packages/shared" }]        // โœ…

// Use extends to share config
"extends": "@tsconfig/node20/tsconfig.json"            // โœ…

// Type-check in CI
npx tsc --noEmit                                       // โœ…

โŒ Don’t Do This:

// Don't turn off strict permanently
"strict": false                                        // โŒ

// Don't mix source and output
// No rootDir / outDir                                  // โš ๏ธ  .js next to .ts

// Don't edit tsconfig without understanding
// Random changes break builds                          // โš ๏ธ

// Don't include node_modules explicitly
"include": ["node_modules"]                            // โŒ

// Don't mix file mode and project mode
tsc file.ts                                            // โš ๏ธ  ignores tsconfig

// Don't commit tsbuildinfo in some setups
git add tsconfig.tsbuildinfo                           // โš ๏ธ  depends on workflow

// Don't use target ES5 for modern Node
"target": "ES5"                                        // โŒ downlevels everything

// Don't duplicate config across monorepo packages
// Copy-pasting a full tsconfig                        // โš ๏ธ  use extends

Common Pitfalls

PitfallProblemSolution
Running tsc file.ts in a projectIgnores tsconfig.jsonRun tsc (no args)
Forgetting rootDir.js next to .tsSet rootDir and outDir
Wrong moduleResolutionImports don’t resolveMatch to environment
strict: false long-termWeak checkingEnable strict
include misses filesNot compiledCheck globs
extends paths wrongConfig not foundPaths resolve from extending file
Ignoring exit codes in CIFalse passing buildsCheck exit codes
Editing node_modulesOverwritten on installNever edit โ€” install
Missing skipLibCheckSlow buildsEnable it
Incompatible target/moduleRuntime errorsMatch to runtime

Real-World Examples

1. Minimal config

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "strict": true
  }
}

2. Library config

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "declaration": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

3. Node app config

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*.ts"]
}

4. Bundler app config (Vite, esbuild)

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "jsx": "react-jsx",
    "noEmit": true
  },
  "include": ["src"]
}

5. Config with extends

{
  "extends": "@tsconfig/node20/tsconfig.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

6. Exclude tests

{
  "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
}

7. Explicit files

{
  "files": ["src/cli.ts", "src/index.ts"]
}

8. Watch and rebuild

npx tsc --watch

9. Type-check in CI

npx tsc --noEmit

10. Emit declarations

"declaration": true

11. Enable source maps

"sourceMap": true

12. Isolated modules

"isolatedModules": true

For bundler-based workflows โ€” ensures each file can be compiled independently.

13. Project references

{
  "references": [
    { "path": "./packages/shared" },
    { "path": "./packages/api" }
  ],
  "files": []
}

14. Composite project

{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

15. Build all projects

npx tsc --build

16. Clean builds

npx tsc --build --clean

17. Check a config without compiling

npx tsc --showConfig

Prints the effective config after merging extends.

18. Find which tsconfig is used

npx tsc --showConfig | head -5

19. Add a build script

"scripts": {
  "build": "tsc",
  "watch": "tsc --watch",
  "typecheck": "tsc --noEmit"
}

20. Print help for a flag

npx tsc --help | grep watch

Visual: Config Resolution

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  tsc (no args)                               โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Find tsconfig.json                          โ”‚
โ”‚  (walk up from cwd)                          โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Read extends chain                          โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Merge compilerOptions                       โ”‚
โ”‚  (child overrides parent)                    โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Apply include/exclude                       โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Compile files                               โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Emit to outDir                              โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: extends Merge

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  tsconfig.base.json                          โ”‚
โ”‚                                              โ”‚
โ”‚  target: ES2022                              โ”‚
โ”‚  strict: true                                โ”‚
โ”‚  esModuleInterop: true                       โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ”‚  extends
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  tsconfig.json                               โ”‚
โ”‚                                              โ”‚
โ”‚  extends: ./tsconfig.base.json               โ”‚
โ”‚  outDir: dist                                โ”‚
โ”‚  rootDir: src                                โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ”‚  merged
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Effective config                            โ”‚
โ”‚                                              โ”‚
โ”‚  target: ES2022          (inherited)         โ”‚
โ”‚  strict: true            (inherited)         โ”‚
โ”‚  esModuleInterop: true   (inherited)         โ”‚
โ”‚  outDir: dist            (own)               โ”‚
โ”‚  rootDir: src            (own)               โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Project References

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  tsconfig.json (root)                        โ”‚
โ”‚                                              โ”‚
โ”‚  references:                                 โ”‚
โ”‚    โ†’ packages/shared                         โ”‚
โ”‚    โ†’ packages/api                            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚                            โ”‚
       โ–ผ                            โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  shared/         โ”‚    โ”‚  api/                โ”‚
โ”‚                  โ”‚    โ”‚                      โ”‚
โ”‚  composite: true โ”‚โ—„โ”€โ”€โ”€โ”‚  references: shared  โ”‚
โ”‚  declaration     โ”‚    โ”‚                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  tsc --build                                 โ”‚
โ”‚                                              โ”‚
โ”‚  Builds shared first, then api               โ”‚
โ”‚  Rebuilds only what changed                  โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Project Layout with tsconfig

my-project/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ index.ts           โ† source
โ”‚   โ””โ”€โ”€ utils.ts
โ”œโ”€โ”€ dist/
โ”‚   โ”œโ”€โ”€ index.js           โ† emitted
โ”‚   โ”œโ”€โ”€ index.d.ts         โ† emitted
โ”‚   โ”œโ”€โ”€ index.js.map       โ† emitted
โ”‚   โ””โ”€โ”€ utils.js
โ”œโ”€โ”€ node_modules/
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ tsconfig.json          โ† compiler config
โ”œโ”€โ”€ tsconfig.base.json     โ† shared (if any)
โ””โ”€โ”€ .gitignore

Visual: Watch Mode Loop

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  tsc --watch                                 โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”œโ”€โ”€โ–บ initial full build                โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ””โ”€โ”€โ–บ watch for file changes            โ”‚
โ”‚                                              โ”‚
โ”‚  Edit src/index.ts                           โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Incremental rebuild                         โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  dist/index.js updated                       โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Strict Mode Effects

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  strict: false                               โ”‚
โ”‚                                              โ”‚
โ”‚  function f(x) { }         โ† implicit any    โ”‚
โ”‚  let name: string = null;  โ† allowed         โ”‚
โ”‚  obj.method();             โ† this unchecked  โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  strict: true                                โ”‚
โ”‚                                              โ”‚
โ”‚  function f(x) { }         โ† error: type x   โ”‚
โ”‚  let name: string = null;  โ† error: null     โ”‚
โ”‚  obj.method();             โ† this checked    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ConceptMeaning
tscThe TypeScript compiler
Project modeCompiles per tsconfig.json
File modeCompiles one file, ignores config
tsconfig.jsonCompiler configuration
compilerOptionsHow the compiler behaves
include / excludeFile selection globs
filesExplicit file list
extendsInherit from another config
referencesProject references for monorepos
compositeEnables referencing
strictEnables all strict sub-flags
targetOutput JS version
moduleOutput module system
outDir / rootDirOutput and source roots

Key takeaways:

  • tsc is the compiler; run it with no args to use the project’s tsconfig.json
  • tsc file.ts ignores tsconfig.json โ€” avoid in real projects
  • tsconfig.json configures the project โ€” strict: true, modern target, and separated outDir/rootDir are the baseline
  • compilerOptions has hundreds of flags โ€” about fifteen matter for most projects
  • include / exclude control which files are compiled โ€” globs, not lists
  • extends shares config between projects โ€” the child overrides the parent
  • references + composite enable incremental monorepo builds
  • tsc --build compiles projects in dependency order
  • tsc --noEmit type-checks without emitting โ€” perfect for CI
  • tsc --watch recompiles on change โ€” for the dev loop
  • Exit codes matter โ€” 0 means success, non-zero means errors
  • Use tsc --showConfig to see the effective merged config

Remember: tsconfig.json is where you decide what TypeScript is for this project โ€” how strict, what target, what files, what output. tsc reads it, applies it, and either produces JavaScript or produces errors. Learn the fifteen options that matter, use extends to share them, and add references when the project grows into a monorepo. Everything else is a variation on this foundation โ€” and you’ll see the same patterns in every TypeScript project you work with.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!