Build Tools: tsc, esbuild, tsx and Vite

Advanced
12 min

Build Tools: tsc, esbuild, tsx and Vite

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: The Reference Compiler

tsc reads tsconfig.json, type-checks every file in the program and emits .js (and optionally .d.ts) files:

bash
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 save

Enable "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 and Native Type Stripping for Development

tsx runs a TypeScript file immediately by transpiling it in memory with esbuild:

bash
npm install --save-dev tsx npx tsx src/server.ts # run once npx tsx watch src/server.ts # restart on file changes

It 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: Fast Transform and Bundle

esbuild compiles and bundles TypeScript roughly 10 to 100 times faster than tsc:

bash
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 # browser

Because 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: Browser Applications

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.

bash
npm create vite@latest my-app -- --template react-ts # or vanilla-ts, vue-ts, svelte-ts cd my-app && npm install && npm run dev

The generated package.json shows the check-then-build pattern:

json
{ "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.

Choosing a Tool

| 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.

Common mistakes

  • Trusting a green esbuild or Vite build as proof of type safety; only tsc checks types.
  • Leaving isolatedModules off with a single-file transpiler, which lets const enum and type re-exports break at runtime.
  • Emitting from both tsc and a bundler into the same dist/; set "noEmit": true when another tool owns the output.
Quick Quiz
Question 1 of 3

Which of these tools performs type checking?

Key Takeaways

  • A TypeScript build has two jobs: type checking (only tsc) and transforming or bundling (any tool).
  • Use tsx, or native Node.js type stripping with erasableSyntaxOnly, for fast development loops.
  • esbuild and swc emit in milliseconds but need isolatedModules and a separate tsc --noEmit check.
  • Vite pairs esbuild-based dev serving with a bundled production build; tsc -b && vite build is the standard script.
  • Publish libraries with 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.

Build Tools: tsc, esbuild, tsx and Vite - TypeScript | CodeYourCraft | CodeYourCraft