CodeFixesHub
    programming tutorial

    Using esbuild or swc for Faster TypeScript Compilation

    Shrink TypeScript build time with esbuild or swc — setup guides, examples, and optimization tips. Speed up dev builds now.

    article details

    Quick Overview

    TypeScript
    Category
    Aug 28
    Published
    18
    Min Read
    2K
    Words
    article summary

    Shrink TypeScript build time with esbuild or swc — setup guides, examples, and optimization tips. Speed up dev builds now.

    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:

    bash
    npm install -D esbuild

    Simple build script (build.js):

    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:

    bash
    npm install -D @swc/core @swc/cli

    A simple CLI invocation:

    bash
    npx swc src -d dist --source-maps inline --config-file .swcrc

    Example .swcrc:

    json
    {
      "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 --noEmit in watch or CI to catch type errors.
    • Use fork-ts-checker-webpack-plugin style 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 dist as a separate step.
    • In CI, run tsc --build to 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:

    js
    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-loader for webpack: keeps many webpack features but speeds up transpilation.
    • @swc/loader for 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 target to 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.ts emission: 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 --noEmit in 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.ts with 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:

    Real-World Applications

    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.

    article completed

    Great Work!

    You've successfully completed this TypeScript tutorial. Ready to explore more concepts and enhance your development skills?

    share this article

    Found This Helpful?

    Share this TypeScript tutorial with your network and help other developers learn!

    continue learning

    Related Articles

    Discover more programming tutorials and solutions related to this topic.

    No related articles found.

    Try browsing our categories for more content.

    Content Sync Status
    Offline
    Changes: 0
    Last sync: 11:20:04 PM
    Next sync: 60s
    Loading CodeFixesHub...