Episode 1 — Fundamentals / 1.25 — TypeScript Essentials
1.25.k — TypeScript Compiler
In one sentence: The TypeScript compiler (
tsc) performs two jobs — type 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
| Flag | Short | Purpose |
|---|---|---|
--watch | -w | Auto-recompile on file changes |
--noEmit | - | Type-check only, produce no output files |
--noEmitOnError | - | Do not emit if there are type errors |
--project path | -p path | Use a specific tsconfig.json |
--build | -b | Build mode for project references |
--declaration | -d | Generate .d.ts declaration files |
--sourceMap | - | Generate .js.map source map files |
--strict | - | Enable all strict type checks |
--init | - | Create a tsconfig.json file |
--version | -v | Print TypeScript version |
--help | -h | Show 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
.tsfiles 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);
~~
| Part | Meaning |
|---|---|
src/app.ts:12:15 | File path, line number, column number |
TS2345 | Error code — unique and searchable |
| Message text | Human-readable description |
| Code snippet | The offending line with underlined portion |
Most common errors and solutions
| Code | Error | Common cause | Fix |
|---|---|---|---|
| TS2322 | Type 'X' is not assignable to type 'Y' | Wrong type assignment | Check the types match |
| TS2339 | Property 'X' does not exist on type 'Y' | Typo or missing property | Fix spelling or add to type |
| TS2345 | Argument of type 'X' not assignable to parameter of type 'Y' | Wrong function argument | Pass the correct type |
| TS2532 | Object is possibly 'undefined' | Missing null check | Add if (obj) or optional chaining ?. |
| TS2531 | Object is possibly 'null' | Missing null check | Add null check or non-null assertion ! |
| TS7006 | Parameter 'X' implicitly has an 'any' type | Missing type annotation (with noImplicitAny) | Add type annotation |
| TS2304 | Cannot find name 'X' | Missing import or declaration | Import the symbol or install @types/* |
| TS2307 | Cannot find module 'X' | Module not installed or path wrong | Install package or fix import path |
| TS2556 | Expected N arguments, got M | Wrong number of arguments | Check function signature |
| TS1005 | ';' expected | Syntax error | Fix the syntax |
| TS2741 | Property 'X' is missing in type 'Y' | Incomplete object literal | Add 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:
stringnot assignable tonumber— the core problem- Property
ageis incompatible — which property - 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:
| Tool | Type checks? | Compiles to JS? | Speed |
|---|---|---|---|
tsc | Yes | Yes | Moderate |
tsc --noEmit | Yes | No | Moderate |
| esbuild | No | Yes | Very fast |
| Vite (uses esbuild) | No | Yes | Very fast |
| SWC (Next.js) | No | Yes | Very fast |
Babel (@babel/preset-typescript) | No | Yes | Fast |
| ts-node | Yes (by default) | Yes (in memory) | Slow |
| tsx (uses esbuild) | No | Yes (in memory) | Fast |
The modern pattern:
- Use a fast tool (esbuild, SWC, Vite) for compilation
- Use
tsc --noEmitfor 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
| Feature | tsc | ts-node | tsx |
|---|---|---|---|
| Purpose | Compile and/or type-check | Run .ts directly | Run .ts directly |
| Type checking | Yes | Yes (default) | No |
| Produces files | Yes (.js output) | No (in-memory) | No (in-memory) |
| Speed | Moderate | Slow | Fast |
| Watch mode | tsc -w (compile only) | With nodemon | tsx watch |
| ESM support | Yes | Requires config | Yes (zero config) |
| Use case | Production build, CI | Development, scripts | Development, 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:
- Type checking is fast and catches obvious errors early
- Linting catches patterns that types do not
- Formatting ensures consistency
- Tests verify behavior
- Build produces the final artifact
Key takeaways
tscdoes two jobs: type checking and emitting JavaScript.- Use
--noEmitwhen a bundler handles compilation;tsconly type-checks. - Watch mode (
tsc -w) provides continuous feedback during development. - Modern bundlers (Vite, esbuild) strip types without checking them — pair with
tsc --noEmit. - Read errors bottom-to-top for nested type errors; search TS error codes online.
- Use incremental compilation for faster rebuilds on large projects.
- A complete workflow: typecheck + lint + format + test + build.
Explain-It Challenge
Explain without notes:
- Why does
tscemit JavaScript even when there are type errors? - When would you use
tsc --noEmitinstead of a regulartscbuild? - Why do Vite and esbuild not catch TypeScript type errors?
Navigation: ← 1.25 Overview · 1.25.l — Linting and Formatting →