Using esbuild or swc for Faster TypeScript Compilation
Introduction
Slow TypeScript builds are one of the most common productivity killers for intermediate developers working on modern web and backend projects. As projects grow, the cost of waiting on full type-check + emit cycles or long bundler rebuilds quickly erodes feedback loops. This guide walks you through using two modern, high-performance toolchains — esbuild and swc — to significantly shorten TypeScript compile and bundle times while preserving correctness and DX.
By the end of this article you'll understand the practical tradeoffs between esbuild and swc, how to integrate either tool into development and CI, options for preserving type-safety and source maps, how to wire them into bundlers and test runners, and concrete performance tuning tips. The tutorial includes step-by-step setup instructions, config examples, watch-mode workflows, monorepo considerations, and troubleshooting. We'll also link to deeper guides on TypeScript compilation speed, runtime overhead, and related best practices so you can build a fast, maintainable toolchain.
Background & Context
Traditional TypeScript compilation (tsc) performs full type checking and may be used together with bundlers like webpack or rollup. While tsc is robust and authoritative for types, it's not optimized for raw transform + bundle speed. esbuild and swc are newer tools, implemented in Go and Rust respectively, that can parse, transform, and emit JavaScript much faster than TypeScript's emitter. They typically focus on fast transpilation (removing or transforming types) and rely on external type checkers (tsc or tsserver) when full type checking is required.
Choosing a faster transformer can drastically reduce rebuild times, especially in watch mode and CI. However, adopting them requires thinking about type-checking strategy, source maps, declaration file generation, and how they interact with other tooling like linters and test runners. For deeper considerations about compilation speed and why optimizing it matters, see our guide on Performance Considerations: TypeScript Compilation Speed.
Key Takeaways
- Use esbuild or swc for fast transpile + bundle times while keeping tsc for authoritative type checks.
- Opt for a parallelized workflow: fast transform in dev, full type-checks in CI or background process.
- Preserve source maps and declaration files where necessary (hybrid strategies are common).
- Integrate with bundlers, test runners, and monorepos carefully to maintain predictable outputs.
- Profile builds and pick tool-specific optimizations (caching, incremental builds, target settings).
Prerequisites & Setup
Before you begin, you should have:
- Node.js (14+ recommended) and npm/yarn/pnpm
- A TypeScript project with a tsconfig.json
- Familiarity with basic build tools (npm scripts, bundlers) and TypeScript compiler concepts
Install the tools you want to try: for esbuild, install the JS API package or CLI; for swc, use the swc CLI and the Node bindings. You may also want to keep tsc installed for type checking and declaration file generation. If you use linters, ensure compatibility — for example, you can combine fast transpilers with existing lint setups explained in our guide on Integrating ESLint with TypeScript Projects (Specific Rules).
Main Tutorial Sections
1) Overview: esbuild vs swc (What each tool does)
esbuild is a bundler and minifier written in Go; it provides a JS API and a CLI. It is designed for speed and simplicity, offering built-in bundling, minification, and an opinionated approach. swc is a fast TypeScript/JS compiler written in Rust that focuses on fast transforms and offers a plugin ecosystem. Both will strip TypeScript types and transpile modern syntax down to your target env.
When choosing: prefer esbuild for extremely fast bundling and simple config in many web projects; choose swc when you need tighter parity with Babel features or more advanced plugin capabilities. For projects that require type declaration output (.d.ts), you'll still rely on tsc or other tools for d.ts file generation.
2) Why faster compilation matters (measuring impact)
Faster builds improve developer feedback loops: less context switching, quicker test runs, and faster PR iteration. Measure baseline times with your current setup using simple npm scripts and a timer. Use tools like time or the bundler's verbose stats to identify slow steps.
For a typical mid-sized app, switching to esbuild or swc can reduce cold builds from tens of seconds to under 5 seconds for transforms. For watch-mode incremental rebuilds, the gains are even larger. Also consider runtime overhead of type systems and transformations; for more on where TypeScript impacts runtime, check Performance Considerations: Runtime Overhead of TypeScript (Minimal).
3) Basic esbuild setup for TypeScript
Install esbuild and create a simple build script:
npm install -D esbuild
Simple build script (build.js):
const esbuild = require('esbuild')
esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
outfile: 'dist/bundle.js',
sourcemap: true,
target: ['es2018']
}).catch(() => process.exit(1))Add an npm script: node build.js. This transforms and bundles quickly. Note: esbuild doesn't emit .d.ts files — use tsc --emitDeclarationOnly if you need declarations.
4) Basic swc setup for TypeScript
Install swc and the Node CLI:
npm install -D @swc/core @swc/cli
A simple CLI invocation:
npx swc src -d dist --source-maps inline --config-file .swcrc
Example .swcrc:
{
"jsc": {
"parser": {"syntax": "typescript", "tsx": false},
"target": "es2018"
},
"sourceMaps": true
}swc focuses on transforming files quickly. Like esbuild, swc doesn't produce .d.ts; run tsc separately for declarations.
5) Preserving type safety: parallel type checking
Because esbuild and swc skip full type checking, adopt a two-process strategy: a fast transform for developer builds + a background or CI type checker. Options:
- Run
tsc --noEmitin watch or CI to catch type errors. - Use
fork-ts-checker-webpack-pluginstyle processes if you integrate with webpack. - Run a continuous background process (CI or developer machine) for types.
This hybrid approach gives near-instant transforms with the safety barrier of a full type check. For larger codebases or monorepos, consider patterns in Managing Types in a Monorepo with TypeScript to centralize type checks.
6) Source maps and debugging strategies
Source maps are essential for debugging. Both tools emit source maps, but you must configure them in tandem with your bundler and dev server. For esbuild set sourcemap: true or sourcemap: 'inline'. For swc configure sourceMaps: true in .swcrc.
When using frameworks or file-serving middlewares, validate that the dev server serves the .map files and that your browser maps locations back to .ts sources. If you use pre-processing (e.g., CSS-in-JS), ensure the full mapping chain preserves correct line numbers.
7) Generating declaration files (.d.ts)
Neither esbuild nor swc produce .d.ts files. To generate declarations:
- Use
tsc --emitDeclarationOnly --outDir distas a separate step. - In CI, run
tsc --buildto produce declarations and enforce API contracts.
A common pipeline is: fast transform + bundle via esbuild/swc for runtime, plus tsc in CI to produce .d.ts and type-check release builds. For advanced declaration handling in complex libs, consult our guide on Writing Declaration Files for Complex JavaScript Libraries.
8) Incremental builds and watch mode
esbuild offers a serve/watch API for very fast rebuilds. Example quick watch:
esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
outfile: 'dist/bundle.js',
sourcemap: true,
incremental: true,
watch: true
}).then(result => { /* result.rebuild() available */ })For swc, use --watch in the CLI or integrate with chokidar to re-run transforms. Combine with a background tsc --watch if you want immediate type feedback. Using incremental build caches and watch modes will dramatically reduce iterative build times.
9) Integrating with bundlers and frameworks
If you already use webpack or rollup, you can replace some parts of the pipeline with esbuild or swc loaders:
esbuild-loaderfor webpack: keeps many webpack features but speeds up transpilation.@swc/loaderfor webpack: swaps Babel out for swc.
When doing this, keep careful control over configuration parity (target, JSX transforms). For frameworks like React, ensure JSX handling is configured properly. If you're reorganizing large apps, follow patterns from Best Practices for Structuring Large TypeScript Projects to make the integration less error-prone.
10) Monorepos, CI, and reproducible builds
In monorepos, you must coordinate type-checking, caching, and artifact outputs across packages. esbuild and swc are great for fast per-package builds, but ensure you centralize type definitions and versioned outputs. Use the approaches in Managing Types in a Monorepo with TypeScript to share types and avoid duplicate .d.ts generation.
In CI, prefer running tsc --build to validate API surfaces and a deterministic build step using the same target and minification options. Cache build outputs and locations of the esbuild/swc caches in CI to accelerate repeated runs.
Advanced Techniques
Once you have a working pipeline, consider these optimizations:
- Use targeted transpilation: set
targetto the lowest necessary platform to reduce transform cost. - Leverage persistent caches (swc caching, esbuild incremental builds) across restarts and CI. For esbuild, use
result.rebuild()returned from build for instant rebuilds. - Offload type checking to a dedicated machine or use a remote caching CI job. For monorepos, incremental builder tools and build caches (like turborepo or Nx) pair well with fast transforms.
- Use selective
.d.tsemission: only emit declarations for packages that are public-facing. - Profile build steps and file watchers; sometimes disk I/O or antivirus can impair performance more than the transform step.
Combining purity practices from Achieving Type Purity and Side-Effect Management in TypeScript with fast transformers helps you keep builds deterministic and easier to cache.
Best Practices & Common Pitfalls
Dos:
- Do run
tsc --noEmitin CI or as a scheduled job to maintain type safety. - Do keep your tsconfig and esbuild/swc target settings in sync to avoid subtle runtime differences.
- Do generate
.d.tswith tsc for public libraries. - Do instrument source maps early to ensure debuggability.
Don'ts:
- Don't rely solely on esbuild/swc for type checks; they are transpilers, not type validators.
- Don't change tsconfig settings that have semantic consequences without checking with
tsc. - Avoid mixing too many different transform pipelines in a single repo; keep a canonical config.
Troubleshooting tips:
- If certain TypeScript features behave differently, run
tscto confirm whether it's a transform issue. - For surprising runtime errors, verify source maps and target settings. Also check third-party type definitions — if you rely on hand-written types, see Typing Third-Party Libraries Without @types (Manual Declaration Files) and Writing Declaration Files for Complex JavaScript Libraries.
Real-World Applications
- Developer local builds: Use esbuild for instant dev servers in React/SPAs where developer speed matters most.
- Library builds: Use swc for predictable transpiles and
tscto generate.d.tsin CI for published packages. - Back-end services: Use swc to compile Node projects quickly and
tscin CI to assert types. For Deno projects, consider Deno's built-in TypeScript handling but when using bundlers or external tools, see Using TypeScript in Deno Projects: A Practical Guide for Intermediate Developers. - Workers and isolated runtimes: For Web Workers or Service Workers where transform speed and small bundles matter, leverage the fast output from esbuild and check patterns in Using TypeScript with Web Workers: A Comprehensive Guide for Intermediate Developers and Using TypeScript with Service Workers: A Practical Guide.
Conclusion & Next Steps
Adopting esbuild or swc can drastically improve TypeScript build times and developer velocity. Use a hybrid approach: fast transforms for dev and authoritative type checks and declaration emission in CI. Start by converting one project or package, measure the gains, and gradually expand. Next, read the compilation performance deep-dive and apply the monorepo and organization patterns linked throughout this guide.
Enhanced FAQ
Q: Should I stop using tsc entirely if I use esbuild or swc?
A: No. Esbuild and swc are fast transpilers but do not perform full type checking nor emit .d.ts files. Continue using tsc for authoritative type-checking and declaration generation, especially in CI or for library releases.
Q: How do I get type checking while keeping instant transforms in dev?
A: Use a two-process strategy: a fast esbuild/swc transform for dev server rebuilds and tsc --noEmit --watch running in parallel. This gives quick edits and type errors reported asynchronously.
Q: Can esbuild or swc generate declaration files (.d.ts)?
A: Not directly. You should run tsc --emitDeclarationOnly or tsc --declaration as a distinct step to generate .d.ts outputs.
Q: Which is faster: esbuild or swc? A: Both are extremely fast; esbuild often has the edge for simple bundling tasks, while swc can be closer to Babel in feature parity. The performance difference is workload-dependent — measure on your codebase.
Q: How do I preserve source maps for accurate stack traces?
A: Enable sourcemaps in your esbuild/swcrc configs and ensure your dev server serves .map files. Use inline maps for local debugging and external maps for CI builds. Test mapping by setting breakpoints in the browser or using node's --enable-source-maps.
Q: What about testing tools and linters? A: Most test runners and linters can be configured to work with esbuild/swc outputs or to use ts-node for tests. Integrate linting as a separate step; our guide on Integrating ESLint with TypeScript Projects (Specific Rules) covers ESLint-specific considerations.
Q: How do I handle monorepos and cross-package types?
A: Centralize shared types in a single package or use project references. Combine fast transforms with centralized tsc --build steps as explained in Managing Types in a Monorepo with TypeScript.
Q: Any caveats regarding runtime behavior?
A: Mostly around language features that may be handled differently across transformers. If you see runtime differences, run tsc to check whether it's a transform mismatch. Also, consider how side-effects and module resolution may be impacted — see Achieving Type Purity and Side-Effect Management in TypeScript for patterns.
Q: Are there gotchas for web workers or service workers? A: Ensure the toolchain outputs worker-compatible bundles (self scope, no DOM references) and that source maps and MIME types are correctly served. For guidance, see Using TypeScript with Web Workers: A Comprehensive Guide for Intermediate Developers and Using TypeScript with Service Workers: A Practical Guide.
Q: How can I measure whether switching is worth it for my project? A: Measure baseline cold and incremental build times, implement a minimal esbuild/swc setup, and compare the change. Also track CI times and developer satisfaction. For broader performance considerations, consult Performance Considerations: TypeScript Compilation Speed to identify hotspots.
