Episode 1 — Fundamentals / 1.25 — TypeScript Essentials

1.25.k — TypeScript Compiler

In one sentence: The TypeScript compiler (tsc) performs two jobstype checking your code for errors and emitting plain JavaScript output — and understanding its modes, flags, and error messages is essential for an efficient development workflow.

Navigation: ← 1.25 Overview · 1.25.l — Linting and Formatting →


1. What tsc does

tsc (TypeScript Compiler) is the official CLI tool for TypeScript. It performs two distinct operations:

┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│  .ts files   │ ───► │     tsc      │ ───► │  .js files   │
│  (source)    │      │              │      │  (output)    │
└──────────────┘      │ 1. Type-check│      └──────────────┘
                      │ 2. Emit JS   │
                      └──────────────┘

Job 1 — Type checking: Analyzes your code, finds type errors, reports them.

Job 2 — Emitting: Strips type annotations and outputs plain JavaScript.

Important: tsc emits JavaScript even if there are type errors (by default). The type errors are warnings, not blockers. To prevent emission on errors, use --noEmitOnError.


2. Basic usage

# Compile a single file
npx tsc hello.ts                  # Produces hello.js in the same directory

# Compile a project (reads tsconfig.json)
npx tsc                            # Compiles all files per 'include' in tsconfig.json

# Compile with specific options
npx tsc --target ES2020 --module commonjs hello.ts

Common flags

FlagShortPurpose
--watch-wAuto-recompile on file changes
--noEmit-Type-check only, produce no output files
--noEmitOnError-Do not emit if there are type errors
--project path-p pathUse a specific tsconfig.json
--build-bBuild mode for project references
--declaration-dGenerate .d.ts declaration files
--sourceMap-Generate .js.map source map files
--strict-Enable all strict type checks
--init-Create a tsconfig.json file
--version-vPrint TypeScript version
--help-hShow all available options
--listFiles-Print all files included in compilation
--pretty-Color and format error output (default: true)

3. Watch mode

Watch mode monitors your source files and recompiles automatically on changes:

npx tsc --watch
# or
npx tsc -w

Output looks like:

[12:00:00] Starting compilation in watch mode...
[12:00:01] Found 0 errors. Watching for file changes.

# Edit a file...

[12:00:15] File change detected. Starting incremental compilation...
[12:00:15] Found 2 errors.

src/app.ts:10:5 - error TS2322: Type 'string' is not assignable to type 'number'.
src/app.ts:15:3 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

Watch mode uses incremental compilation — only recompiles changed files and their dependents, making it fast.


4. --noEmit flag — type-check only

When using a bundler (Vite, esbuild, Next.js) that handles TypeScript compilation, use tsc purely for type checking:

npx tsc --noEmit

This:

  • Reads all .ts files per tsconfig.json
  • Runs all type checks
  • Reports errors
  • Does NOT produce any output files

Why this matters: Modern bundlers like Vite and esbuild are much faster than tsc at producing JavaScript because they only strip types without full type analysis. The trade-off is they skip type checking. The recommended workflow:

Development:  Bundler handles compilation (fast)
              tsc --noEmit for type checking (thorough)

Build:        tsc --noEmit && vite build
              ↑ type-check   ↑ compile and bundle

5. Build mode — project references

For monorepos or projects with multiple tsconfigs, use --build:

npx tsc --build
# or
npx tsc -b

Project references let you split a large project into smaller pieces:

// packages/shared/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist",
    "declaration": true
  }
}

// packages/app/tsconfig.json
{
  "references": [
    { "path": "../shared" }
  ],
  "compilerOptions": {
    "outDir": "./dist"
  }
}
# Build all referenced projects in dependency order
npx tsc -b packages/app

6. Understanding compiler errors

Error format

src/app.ts:12:15 - error TS2345: Argument of type 'number' is not assignable
  to parameter of type 'string'.

12   greet(42);
               ~~
PartMeaning
src/app.ts:12:15File path, line number, column number
TS2345Error code — unique and searchable
Message textHuman-readable description
Code snippetThe offending line with underlined portion

Most common errors and solutions

CodeErrorCommon causeFix
TS2322Type 'X' is not assignable to type 'Y'Wrong type assignmentCheck the types match
TS2339Property 'X' does not exist on type 'Y'Typo or missing propertyFix spelling or add to type
TS2345Argument of type 'X' not assignable to parameter of type 'Y'Wrong function argumentPass the correct type
TS2532Object is possibly 'undefined'Missing null checkAdd if (obj) or optional chaining ?.
TS2531Object is possibly 'null'Missing null checkAdd null check or non-null assertion !
TS7006Parameter 'X' implicitly has an 'any' typeMissing type annotation (with noImplicitAny)Add type annotation
TS2304Cannot find name 'X'Missing import or declarationImport the symbol or install @types/*
TS2307Cannot find module 'X'Module not installed or path wrongInstall package or fix import path
TS2556Expected N arguments, got MWrong number of argumentsCheck function signature
TS1005';' expectedSyntax errorFix the syntax
TS2741Property 'X' is missing in type 'Y'Incomplete object literalAdd missing properties

Reading complex errors (bottom-to-top)

error TS2322: Type '{ name: string; age: string; role: string; }' is not assignable
  to type 'Employee'.
  Types of property 'age' are incompatible.
    Type 'string' is not assignable to type 'number'.

Read from bottom to top:

  1. string not assignable to number — the core problem
  2. Property age is incompatible — which property
  3. The object is not assignable to Employee — the context

Fix: Change age from a string to a number.

Searching for error codes

  • Google: "TS2345" — find explanations and solutions
  • TypeScript docs: typescriptlang.org/tsconfig — search by code
  • VS Code: hover over the error to see the code, then Ctrl+Click for more info

7. Type checking vs compilation

Key insight: tsc does both type checking and compilation, but modern tools often split these responsibilities:

ToolType checks?Compiles to JS?Speed
tscYesYesModerate
tsc --noEmitYesNoModerate
esbuildNoYesVery fast
Vite (uses esbuild)NoYesVery fast
SWC (Next.js)NoYesVery fast
Babel (@babel/preset-typescript)NoYesFast
ts-nodeYes (by default)Yes (in memory)Slow
tsx (uses esbuild)NoYes (in memory)Fast

The modern pattern:

  • Use a fast tool (esbuild, SWC, Vite) for compilation
  • Use tsc --noEmit for type checking
  • Run both in CI/CD

8. When bundlers skip type checking

Vite, esbuild, and SWC transform TypeScript to JavaScript by simply stripping types — they do not analyze or validate them. This means:

// This code has a type error:
const x: number = "hello";  // TS2322

// esbuild/Vite will strip types and output:
const x = "hello";  // No error — runs fine (but wrong!)

This is why tsc --noEmit matters — it catches errors that your bundler silently ignores.


9. tsc vs tsx vs ts-node comparison

Featuretscts-nodetsx
PurposeCompile and/or type-checkRun .ts directlyRun .ts directly
Type checkingYesYes (default)No
Produces filesYes (.js output)No (in-memory)No (in-memory)
SpeedModerateSlowFast
Watch modetsc -w (compile only)With nodemontsx watch
ESM supportYesRequires configYes (zero config)
Use caseProduction build, CIDevelopment, scriptsDevelopment, scripts

Typical development workflow

# Terminal 1: Fast execution with tsx
npx tsx watch src/index.ts

# Terminal 2: Type checking with tsc
npx tsc --noEmit --watch

Or combine in package.json:

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "typecheck": "tsc --noEmit",
    "typecheck:watch": "tsc --noEmit --watch",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

10. Incremental compilation

Enable incremental compilation for faster rebuilds:

{
  "compilerOptions": {
    "incremental": true
  }
}

This creates a .tsbuildinfo file that caches information from the previous compilation. On subsequent runs, tsc only recompiles files that changed and their dependents.

Impact: Can reduce rebuild time from seconds to milliseconds on large projects.

Add .tsbuildinfo to .gitignore:

# .gitignore
*.tsbuildinfo

11. Real workflow: lint + typecheck + build pipeline

A complete CI/CD pipeline:

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "lint": "eslint src/ --ext .ts,.tsx",
    "format:check": "prettier --check src/",
    "test": "vitest run",
    "build": "tsc --noEmit && vite build",
    "ci": "npm run typecheck && npm run lint && npm run format:check && npm run test && npm run build"
  }
}
CI Pipeline:
  1. typecheck  → tsc --noEmit (catch type errors)
  2. lint       → eslint (catch code quality issues)
  3. format     → prettier --check (ensure consistent style)
  4. test       → vitest (run tests)
  5. build      → vite build (produce production bundle)

Why this order:

  1. Type checking is fast and catches obvious errors early
  2. Linting catches patterns that types do not
  3. Formatting ensures consistency
  4. Tests verify behavior
  5. Build produces the final artifact

Key takeaways

  1. tsc does two jobs: type checking and emitting JavaScript.
  2. Use --noEmit when a bundler handles compilation; tsc only type-checks.
  3. Watch mode (tsc -w) provides continuous feedback during development.
  4. Modern bundlers (Vite, esbuild) strip types without checking them — pair with tsc --noEmit.
  5. Read errors bottom-to-top for nested type errors; search TS error codes online.
  6. Use incremental compilation for faster rebuilds on large projects.
  7. A complete workflow: typecheck + lint + format + test + build.

Explain-It Challenge

Explain without notes:

  1. Why does tsc emit JavaScript even when there are type errors?
  2. When would you use tsc --noEmit instead of a regular tsc build?
  3. Why do Vite and esbuild not catch TypeScript type errors?

Navigation: ← 1.25 Overview · 1.25.l — Linting and Formatting →