A TypeScript build has two separate jobs: checking the types and transforming the code into JavaScript the runtime can execute, possibly bundling many files into one. tsc does both but is comparatively slow at the second; esbuild, swc, tsx and Vite do the second in milliseconds but never check anything. Understanding that split lets you pick the right tool for a library, a Node.js service or a browser app. This lesson compares the options and shows the scripts real projects use.
tsc reads tsconfig.json, type-checks every file in the program and emits .js (and optionally .d.ts) files:
npx tsc # check and emit according to tsconfig.json
npx tsc --noEmit # check only, exit code 1 on errors (ideal for CI)
npx tsc --watch # recheck on saveEnable "incremental": true to cache results in a .tsbuildinfo file and speed up repeated runs. tsc is the right choice when you publish a library (it emits declaration files), when the project is small, or as the check step alongside a faster emitter. It does not bundle or minify.
tsx runs a TypeScript file immediately by transpiling it in memory with esbuild:
npm install --save-dev tsx
npx tsx src/server.ts # run once
npx tsx watch src/server.ts # restart on file changesIt replaced ts-node in most projects because it needs no configuration, handles ESM and CommonJS alike, and starts instantly. Nothing is type-checked; your editor and tsc --noEmit do that.
Node.js 22.18 and later (and all 24.x releases) execute .ts files directly by stripping type annotations, so node src/server.ts works without any tool. Only erasable syntax is supported: enums, namespaces with runtime code, parameter properties and import x = require() are rejected. Set "erasableSyntaxOnly": true in tsconfig.json (TypeScript 5.8) so the compiler flags those constructs for you.
esbuild compiles and bundles TypeScript roughly 10 to 100 times faster than tsc:
npm install --save-dev esbuild
npx esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js
npx esbuild src/app.ts --bundle --minify --sourcemap --outdir=public/js # browserBecause it processes one file at a time, it requires code that is safe to transpile in isolation. Set "isolatedModules": true and "verbatimModuleSyntax": true so tsc reports anything esbuild would get wrong, such as re-exporting a type without export type. esbuild cannot emit .d.ts files; libraries that bundle with esbuild use tsc --emitDeclarationOnly or a wrapper such as tsup for declarations. swc is a comparable alternative with Babel-style plugins.
Vite is the standard front-end toolchain. During development it serves each module on demand, transpiling TypeScript with esbuild; for production it bundles through its bundler with tree-shaking and code splitting.
npm create vite@latest my-app -- --template react-ts # or vanilla-ts, vue-ts, svelte-ts
cd my-app && npm install && npm run devThe generated package.json shows the check-then-build pattern:
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
}
}tsc -b type-checks (the templates use project references with noEmit), then vite build produces dist/. Vite's tsconfig.json uses "moduleResolution": "bundler" and "isolatedModules": true, matching how it resolves and transforms files. The vite-plugin-checker package can surface type errors in the dev overlay if you want them earlier than the build.
| Need | Tool | Type checks? | Emits .d.ts? |
|---|---|---|---|
| Publish a library | tsc (or tsup for bundles) | Yes | Yes |
| Run a script or dev server | tsx or node file.ts | No | No |
| Bundle a Node.js service | esbuild + tsc --noEmit | Via tsc | No |
| Browser application | Vite + tsc -b | Via tsc | No |
| CI gate | tsc --noEmit | Yes | No |
The recurring pattern: one fast tool emits, tsc checks. Wire both into package.json so the check cannot be skipped by accident, and make CI run the check even when the build succeeds.
tsc checks types.isolatedModules off with a single-file transpiler, which lets const enum and type re-exports break at runtime.tsc and a bundler into the same dist/; set "noEmit": true when another tool owns the output.Which of these tools performs type checking?
tsc) and transforming or bundling (any tool).tsx, or native Node.js type stripping with erasableSyntaxOnly, for fast development loops.isolatedModules and a separate tsc --noEmit check.tsc -b && vite build is the standard script.tsc (or tsup) so consumers receive declaration files.Next lesson: Project References and Monorepos — split a large codebase into independently checked projects that build in order.