The TypeScript Compiler (tsc)
tsc is the TypeScript compiler. It reads your .ts source files, performs
type-checking, and emits plain JavaScript. Understanding what tsc does — and
what it does not do — is essential for working with TypeScript effectively.
What tsc Actually Does
tsc does two distinct jobs in a single pass:
- Type-checking — reads your code, builds a type model, and reports any inconsistencies as errors
- Transpilation — converts TypeScript syntax to JavaScript that a runtime can execute
These two jobs are related but separable. You can ask tsc to only type-check (no
emit), or to emit without failing on errors. Most of the time you want both.
tsc is a compiler, not a bundler. It does not bundle files together, tree-shake dead code, or minify output. For those tasks use webpack, Vite, esbuild, or Rollup — usually alongside or instead of running tsc directly.The Compilation Pipeline
When you run tsc, it executes three stages internally:
Parsing — reads the .ts files and builds an Abstract Syntax Tree (AST). Syntax errors are caught here.
Type checking — walks the AST, resolves names and types, and reports type errors. This is the heart of TypeScript.
Emit — walks the type-checked AST and writes out .js (and optionally .d.ts declaration files). Type annotations are erased.
Only the third stage produces files. The first two stages are pure analysis. If you
pass --noEmit, the compiler skips stage 3 entirely — useful in CI to validate types
without generating output.
Basic Usage — Single File
The simplest way to use tsc is to pass a file directly:
npx tsc hello.ts
This compiles hello.ts to hello.js in the same directory. When you pass a file
directly on the command line, tsc ignores any tsconfig.json in the project —
all settings come from CLI flags only.
tsc bypasses tsconfig.json. In a real project you almost always want to run npx tsc with no arguments so it picks up the config file.Basic Usage — With tsconfig.json
Run tsc with no arguments from the project root to use tsconfig.json:
npx tsc
tsc searches for tsconfig.json starting from the current directory and walking
up the directory tree. You can also specify the config file explicitly:
npx tsc --project tsconfig.prod.json # or the short form: npx tsc -p tsconfig.prod.json
Important CLI Flags
Flag | What it does | Example |
|---|---|---|
--target | JavaScript version to emit (ES3, ES5, ES2015...ES2022, ESNext) | --target ES2020 |
--module | Module system for output (commonjs, esm, node16, nodenext) | --module commonjs |
--outDir | Where to put emitted .js files | --outDir dist |
--rootDir | Root of input .ts files (mirrors structure in outDir) | --rootDir src |
--strict | Enable all strict type-checking flags at once | --strict |
--watch | Recompile when files change | --watch or -w |
--noEmit | Type-check only; do not emit any files | --noEmit |
--declaration | Also emit .d.ts type declaration files | --declaration |
--sourceMap | Generate .js.map source map files | --sourceMap |
--allowJs | Also process .js files (for migrating JS codebases) | --allowJs |
--checkJs | Type-check .js files too (use with --allowJs) | --checkJs |
--esModuleInterop | Allow default imports from CommonJS modules | --esModuleInterop |
--skipLibCheck | Skip type checking of all .d.ts files | --skipLibCheck |
--project / -p | Path to a tsconfig.json file | -p tsconfig.prod.json |
--target: Controlling Output JavaScript
The --target flag tells tsc which JavaScript version to emit. TypeScript will
downlevel (polyfill) syntax features not available in the target:
// Source (TypeScript)
const greet = (name: string): string => `Hello, ${name}!`;# Target ES3 (very old browsers) npx tsc --target ES3 hello.ts
// Emitted with --target ES3
var greet = function (name) { return "Hello, " + name + "!"; };# Target ES2020 (modern Node / browsers) npx tsc --target ES2020 hello.ts
// Emitted with --target ES2020
const greet = (name) => `Hello, ${name}!`;--target ES2022 or--target ESNext. For libraries that must support older browsers, use --target ES2015 or --target ES5.--module: Module System
The --module flag controls how TypeScript emits import / export statements:
Value | Use case |
|---|---|
commonjs | Node.js CJS (require / module.exports). Most common for Node. |
esm | Pure ESM output. Use with --target ES2015+. |
node16 / nodenext | Node.js 12+ dual-mode (detects .mjs/.cjs). Recommended for modern Node projects. |
preserve | Keep import/export as-is (let a bundler handle them). Good for Vite/webpack. |
none | No module system — for simple scripts. |
# For a Node.js backend npx tsc --target ES2022 --module node16 # For a Vite/webpack frontend (let the bundler handle modules) npx tsc --target ES2020 --module preserve --noEmit
--outDir and --rootDir
In a real project you want source files separate from compiled output. Use --outDir
and --rootDir together:
npx tsc --rootDir src --outDir dist
With this configuration, src/index.ts compiles to dist/index.js, and
src/utils/helpers.ts compiles to dist/utils/helpers.js — the directory structure
is mirrored.
--strict: The Most Important Flag
--strict is a shorthand that enables a bundle of strict checking options. Without
it, TypeScript lets many unsafe patterns slide silently. Always use it on new projects:
strictNullChecks — null and undefined are not assignable to other types
noImplicitAny — error when a type would be implicitly inferred as any
strictFunctionTypes — stricter checking of function parameter types
strictBindCallApply — stricter checking of bind, call, apply
strictPropertyInitialization — class properties must be initialized in the constructor
noImplicitThis — error when this has an implicit any type
alwaysStrict — emits "use strict" in all output files
# Always use --strict in new projects npx tsc --strict
--watch: Development Mode
During development, you do not want to run tsc manually after every change.
--watch mode keeps tsc running and recompiles whenever a file changes:
npx tsc --watch # or the short form npx tsc -w
[12:34:56] Starting compilation in watch mode... [12:34:57] Found 0 errors. Watching for file changes. [12:35:12] File change detected. Starting incremental compilation... [12:35:12] Found 0 errors. Watching for file changes.
In watch mode, tsc does incremental compilation — it only recompiles files that
changed and their dependents, making each rebuild fast.
--noEmit: Type Checking Without Output
--noEmit tells tsc to type-check everything but produce no output files. This is
extremely useful in two scenarios:
CI pipelines — validate types without needing the build output
Projects using a bundler (Vite, webpack, esbuild) that handles transpilation — use tsc only for type checking
# Type-check the whole project, emit nothing npx tsc --noEmit # Type-check in watch mode (great during development with Vite) npx tsc --noEmit --watch
tsc --noEmit as a separate type-check step. This gives you the fastest possible dev server plus full type safety on demand.Reading Error Messages
TypeScript error messages follow a consistent format. Learning to read them quickly is a core skill:
src/utils/format.ts:12:5 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
12 formatName(userId);
~~~~~~~~~Breaking this down:
src/utils/format.ts— file where the error occurs12:5— line 12, column 5error— severity (alsowarningormessage)TS2345— error code, useful for searching the TypeScript documentation- Description — plain English explanation of the type mismatch
- The squiggly line — points at the exact token causing the error
Multiple Errors — Triage Tips
In a large project tsc can emit dozens of errors at once. Here is how to triage:
Fix errors in dependency order — a type error in a utility function cascades to every caller. Fix the root cause first.
Errors in .d.ts files often mean a missing type package — run npm install @types/node (or the relevant package).
TS2304 ("Cannot find name X") usually means a missing import or missing type declaration.
TS2307 ("Cannot find module X") means the module path is wrong or the package is not installed.
Count the unique error codes — 50 errors might be just 2 root causes.
Exit Codes
tsc follows standard Unix exit code conventions:
Exit code | Meaning |
|---|---|
0 | Success — no errors found, output emitted (or --noEmit passed) |
1 | Configuration error — bad tsconfig, missing input files, invalid flags |
2 | Type errors — compilation failed due to type-checking errors |
npx tsc --noEmit
echo "Exit code: $?"
# Use in CI to fail the build on type errors
npx tsc --noEmit || { echo "Type errors found"; exit 1; }Type Checking vs Transpilation
These two things are related but different, and many developers conflate them:
Type Checking | Transpilation | |
|---|---|---|
What it does | Analyzes types and reports errors | Converts .ts syntax to .js syntax |
Output | Error messages | .js files (and optionally .d.ts files) |
Controlled by | Type annotation correctness | --target, --module flags |
Can be skipped? | With --transpileOnly (ts-node) | With --noEmit |
Runtime effect | None — types are erased | Determines what JavaScript engines receive |
Tools like esbuild and tsx only transpile — they strip type annotations but do
not check types at all. They are fast but give you no safety guarantee. tsc does
both. In many modern setups, transpilation is handled by a fast tool (esbuild/SWC)
and type checking is a separate tsc --noEmit step.
Managing tsc Versions
Keep TypeScript pinned in your package.json and use the local version via npx
rather than a globally installed tsc:
# Check which version is installed locally npx tsc --version # Install a specific version npm install --save-dev typescript@5.4.5
{
"scripts": {
"type-check": "tsc --noEmit",
"build": "tsc",
"dev": "tsc --watch"
},
"devDependencies": {
"typescript": "^5.5.0"
}
}node_modules) and VS Code's built-in version. Always prefer the workspace version so editor and CLI use the same compiler.Practical tsc Commands Cheatsheet
# Compile a single file npx tsc hello.ts # Compile the whole project (uses tsconfig.json) npx tsc # Type-check only, no output npx tsc --noEmit # Watch mode for development npx tsc --watch # Watch + no emit (type-check as you type) npx tsc --noEmit --watch # Compile with specific options npx tsc --target ES2020 --module commonjs --outDir dist # Use a custom config file npx tsc -p tsconfig.prod.json # Show compiler version npx tsc --version # Show all available compiler options npx tsc --help --all
tsc does, how to read its error messages, and which flags matter most. The key insight: type checking and transpilation are two separate jobs that tsc does together — but you can split them apart for faster development workflows.