Right, let’s talk about speed. You’ve felt it, that agonizing lag between hitting save and your TypeScript project finishing its compile cycle. It starts as a minor annoyance and slowly grows into a soul-crushing time sink. The culprit? tsc, the official TypeScript compiler, is doing a lot of heavy lifting for you: type checking, transpiling to your target JavaScript, handling all those fancy tsconfig.json options. It’s brilliant, but it’s a scholar, not a sprinter.

This is where we cheat. Well, it’s not really cheating. It’s just acknowledging that most of the time in your development loop—especially in a hot-reload scenario—you don’t need the full, pedantic type check on every single save. You need that code converted from TypeScript to JavaScript, and you need it now. Enter the transpile-only build. We offload the incredibly CPU-intensive job of type checking to your IDE and the occasional full tsc --noEmit run, and we hand the simple, repetitive task of stripping out types to a tool built for raw speed.

Why SWC and esbuild Leave tsc in the Dust

tsc is written in TypeScript itself. It’s a complex, feature-rich application. SWC (Speedy Web Compiler) and esbuild, on the other hand, are written in systems-level languages—SWC in Rust, esbuild in Go. This isn’t just a language holy war; it has real, tangible consequences. These languages compile to native machine code and are designed for performance, with incredibly fast startup times and optimal low-level memory management. They are basically a Formula 1 car compared to tsc’s very smart, very capable family sedan. They do one job—transpiling—and they do it orders of magnitude faster.

The key thing to understand is that they are transpilers, not compilers. They transform your .ts/.tsx syntax into .js/.jsx. They do not perform type checking. That’s your job, which you should be doing in your editor and your CI/CD pipeline. This separation of concerns is the entire secret.

Setting Up a Speedy Development Build

You’ll typically use these tools as part of your build pipeline. For a development build, you can use them directly or via a integration like with Webpack, Vite, or ts-node. Let’s look at a direct example with esbuild first, because its API is brilliantly simple.

// build.js
import * as esbuild from 'esbuild';

const ctx = await esbuild.context({
  entryPoints: ['src/index.ts'],
  bundle: true, // Often you want this for a dev build
  outdir: 'dist',
  platform: 'node', // or 'browser'
  format: 'esm',
  sourcemap: true, // Crucial for debugging
  tsconfig: './tsconfig.json', // It'll respect most basic settings like `target`
  // This is the magic flag: ignore types, just strip 'em
  legalComments: 'inline',
});

// Start the dev server and enable watch mode
await ctx.watch();
console.log('Watching for changes...');

You’d run this with node build.js. The first build is instant, and subsequent changes feel near-instantaneous. The tsconfig option is important—it tells esbuild your target JS version (e.g., ES2020), so it’s not just stripping types but also doing the necessary syntax transformation.

Integrating with ts-node for Blazing-Fast Execution

If you’re running Node.js scripts directly, ts-node is the usual tool. And it can use SWC under the hood. The performance difference is staggering.

# Install the necessary packages
npm install -D ts-node @swc/core @swc/helpers

Instead of the slow default, you can now run your script with:

npx ts-node --swc my-script.ts

This command bypasses tsc entirely. ts-node handles the resolution, and --swc tells it to hand the transpilation step over to the SWC compiler. It’s so fast you might not even notice it ran.

The Inevitable “But What About…” Pitfalls

Ah, you’re smart. You’re thinking, “This sounds too good to be true.” And you’re right. There are trade-offs, and you must understand them.

  1. No Type Safety: I can’t stress this enough. A transpile-only build will happily generate JavaScript from TypeScript code that you and I know would fail a type check. It will only fail on genuine syntax errors. This is not your production build. This is for development speed. Your CI pipeline must still run tsc --noEmit or a equivalent type-checking step.
  2. Limited tsconfig.json Support: These tools are not 100% feature-compatible with tsc. They support the big, important options (target, jsx, baseUrl, paths), but more obscure options might be ignored. SWC has a compatibility table, and esbuild is very explicit about what it supports. Always check the docs. Your experimentalDecorators might work, but your emitDecoratorMetadata probably won’t. This is the price of speed.
  3. The Path of Most Resistance: The real-world codebase is a messy place. The moment you have a complex custom transformer or a plugin that tsc relies on, you might hit a wall. These tools have plugin systems (esbuild’s is excellent, SWC’s is powerful but more complex), but porting logic over is non-trivial work.

So, the best practice? Use swc or esbuild for your development watch mode and for building your application bundles where type checking has already been done elsewhere. Keep using tsc for type checking, generating declaration files (.d.ts), and for libraries where those declaration files are your primary output. This hybrid approach gives you the blistering speed of a native transpiler where it matters most, while retaining the rigorous correctness of the official compiler where it’s absolutely critical. It’s not a choice between one or the other; it’s about making them work together.