Skip to content

The Compiler & Tooling

Everything you learned about types is enforced by one program: the TypeScript compiler, tsc. But tsc does two jobs that are worth separating in your head — it checks your types and it emits JavaScript. Modern setups often split these: a bundler emits the JavaScript fast, and tsc checks the types separately.

Your tsconfig.json is where you decide how strict the checker is, which JavaScript features to target, how modules are resolved, and what (if anything) gets emitted. Getting this file right is the difference between TypeScript catching bugs and TypeScript quietly waving them through.

LessonWhat you’ll learn
tsconfig, deepThe high-value flags — strict and its family, target, module, and the strict-adjacent options
Modules & resolutionESM vs CJS, and how tsc finds the files you import
Declaration filesWhat .d.ts files are, how to write them, and where @types comes from
Build & emittsc vs bundlers, isolatedModules, type-only imports, and project references
flowchart LR
  src["source .ts / .tsx"] --> tsc["tsc"]
  tsc -->|job 1| check["type-check
report errors"]
  tsc -->|job 2| emit["emit .js and .d.ts
(types erased)"]
  check --> ci["run in CI to catch bugs"]
  emit --> ship["run or bundle the JS"]
tsc does two independent jobs

Keep these two jobs distinct and the whole toolchain makes sense. A bundler like esbuild can do job 2 in milliseconds because it just strips the types without understanding them — which means it will happily emit code that does not type-check. That is why the standard advice is: let the bundler emit, and run tsc --noEmit in CI to actually check. The rest of this module unpacks the config that controls both jobs.

What two jobs does the TypeScript compiler perform?
Why can a bundler like esbuild emit JavaScript that does not type-check?
What is the recommended way to actually catch type errors when a bundler does the emit?