How it works
The explanation, with examples you can run.
Who does what
| Tool | Strips types | Checks types |
|---|---|---|
tsc | Yes (unless noEmit) | Yes |
| esbuild, swc, Babel | Yes, per file | No |
| Vite, and most framework dev servers | Yes, through esbuild or swc | No |
node file.ts (22.18+, 23.6+) | Yes, replaces types with spaces | No |
| Your editor | No | Yes, through the TypeScript language service |
A common setup lets the bundler produce the JavaScript and runs tsc --noEmit as a separate step in CI (and often in a pre-push hook). The bundler stays fast, and the build still fails on type errors.
Writing code a per-file tool can compile
A tool that sees one file at a time can't know whether an imported name is a type or a value, or what value a const enum from another file has. Two settings report code it would get wrong:
isolatedModuleserrors on constructs that need information from other files, such as re-exporting a type withoutexport type.verbatimModuleSyntax(TypeScript 5.0) goes further: imports and exports are kept exactly as written unless markedtype, so you write what you mean.
import type { User } from "./types.js" // removed entirely from the outputimport { formatName } from "./format.js" // kept: it's a valueerasableSyntaxOnly (5.8) adds the last piece for type-stripping tools: no enum, no namespace with values, no parameter properties, so removing types is all that is left to do.
module, moduleResolution and imports
These options don't change your code much; they tell the checker how the real host will find files, so that it accepts the imports that will work and rejects the ones that won't.
"module": "nodenext", for code that Node runs directly. It follows Node's rules: a.jsfile is ESM or CommonJS according topackage.json"type", and relative ES module imports need a file extension. You write the extension of the file that will exist at runtime (./util.js), and TypeScript maps it back toutil.ts."moduleResolution": "bundler"(with"module": "esnext"), for code that a bundler resolves. Extensionless imports andindexfiles are fine, because the bundler allows them.
Other settings that commonly matter:
targetsets which JavaScript syntax the output may use;libsets which built-in APIs the checker assumes exist (DOMfor browsers). They are separate: a moderntargetdoes not add a polyfill for an API the browser lacks.outDirandrootDirdecide where emitted files go;declarationemits.d.tsfiles for libraries.noEmitis for projects where another tool produces the JavaScript.
One tsconfig.json describes one environment. A repository with browser code, server code and tests usually needs several, connected with extends or project references, so that window isn't available in server code and process isn't assumed in the browser.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.