The TypeScript Compiler (tsc)
tsc is the official TypeScript compiler, and understanding what it does demystifies the whole toolchain. It performs two distinct jobs: type-checking (verifying your code obeys its types) and emitting (transpiling .ts to .js, optionally downleveling modern syntax to older targets). Modern projects often split these responsibilities — fast bundlers do the emitting while tsc is used purely as a type-checker. This page covers running tsc, the everyday flags, how it finds your tsconfig.json, and the type-check-only workflow that powers most real pipelines.
Two jobs: check and emit
Conceptually, tsc reads your source, builds a type model, reports any violations, and then writes JavaScript output. Either job can be run independently.
npx tsc # check + emit, using tsconfig.json
npx tsc --noEmit # check only, write nothing
Separating these is the key insight: you can let another tool produce the JavaScript and still rely on tsc for the authoritative type verdict.
Running with a tsconfig.json
When you run tsc with no file arguments, it looks for a tsconfig.json in the current directory (then upward) and compiles the whole project according to that config. This is the normal way to build a real project.
npx tsc # uses ./tsconfig.json
npx tsc --project ./tsconfig.build.json # use a specific config
If you instead pass file names (tsc hello.ts), tsc ignores tsconfig.json entirely and uses only command-line flags — useful for one-off compiles but not for projects.
Everyday compiler flags
Most options live in tsconfig.json, but a handful are commonly passed on the command line. These override or supplement the config.
npx tsc --watch # recompile on save
npx tsc --noEmit # type-check only
npx tsc --outDir dist # write output to dist/
npx tsc --target es2022 # JS version to emit
npx tsc --strict # enable all strict checks
| Flag | Purpose |
|---|---|
--watch / -w | Recompile automatically on file changes |
--noEmit | Type-check only; produce no .js files |
--outDir | Directory for emitted JavaScript |
--target | ECMAScript version of the output |
--module | Module system of the output (e.g. esnext, commonjs) |
--strict | Turn on the full suite of strict type checks |
--declaration | Also emit .d.ts type declaration files |
--project / -p | Use a specific tsconfig file |
Type-check-only workflows
In modern stacks, a bundler (Vite, esbuild, SWC) or a runtime (tsx, Bun) handles transpilation because it is much faster. Those tools generally do not type-check. So the standard pattern is to add a separate script that runs tsc in no-emit mode.
// package.json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"typecheck": "tsc --noEmit"
}
}
npm run typecheck # fails CI if any type error exists
This division keeps your dev loop fast while guaranteeing the type system is still enforced in CI.
Bundlers and
tsx/esbuildtranspile each file in isolation and skip cross-file type checking for speed. Never assume “it ran, so it’s typed correctly” — always includetsc --noEmitsomewhere in your pipeline.
Incremental and project builds
For large codebases, tsc can cache results between runs and orchestrate multiple sub-projects, which speeds up repeated builds substantially.
npx tsc --incremental # cache build info for faster rebuilds
npx tsc --build # build project references in order
The --build (or -b) mode reads references in your tsconfig and compiles dependencies in the right order, only rebuilding what changed. Combined with --incremental, it makes monorepo-scale type-checking practical.
Reading compiler output
tsc errors include a stable code (like TS2345), the file and position, and a human-readable message. The codes are searchable and consistent across versions.
src/cart.ts:12:18 - error TS2345: Argument of type 'string' is not
assignable to parameter of type 'number'.
12 addItem(price, "2");
~~~
Found 1 error in src/cart.ts:12
The caret/tilde underline points at the exact expression at fault, which makes diagnosing even complex generic errors far more approachable.
Best Practices
- Run
tscagainst atsconfig.jsonfor projects; reserve file-argument compiles for one-offs. - Use
tsc --noEmitas a dedicatedtypecheckscript and run it in CI. - Let a fast bundler or
tsxhandle transpilation whiletscowns type verification. - Enable
--incremental(and--buildfor monorepos) to speed up large rebuilds. - Emit
.d.tsfiles with--declarationwhen publishing a library. - Search error codes like
TS2345when a message is unfamiliar — they are well documented.