What Are esbuild and SWC?
esbuild is an extremely fast JavaScript bundler, minifier, and transpiler written in Go. It handles TypeScript, JSX, and modern syntax out of the box and can bundle entire applications with a single command. SWC (Speedy Web Compiler) is a Rust-based platform for fast transpilation and compilation of JavaScript/TypeScript. SWC focuses on transforming code (parsing, emitting target-specific JavaScript) and is typically used as a compiler rather than a full bundler. Both tools leave traditional solutions like Babel and webpack’s default loaders far behind in terms of raw speed.
While esbuild can both bundle and transpile, SWC is primarily a transpiler that integrates deeply into higher‑level tools: you’ll find it inside Next.js, as a webpack loader (swc-loader), as a Jest transformer (@swc/jest), or as a Node.js runtime hook (@swc-node/register). Understanding this distinction is key to a successful migration.
Why Migrate from esbuild to SWC?
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →SWC has become the default compiler in major frameworks like Next.js and is increasingly adopted across the JavaScript ecosystem. Migrating gives you:
- Rust‑native performance that remains consistently fast, even in large monorepos.
- Deeper ecosystem integration – first‑class support for Jest, webpack, and Node.js runtime compilation without extra glue.
- Plugin system and custom transforms – SWC supports Rust‑based plugins for advanced use cases like emotion/styled‑components, relay, or custom decorators.
- Simpler configuration – one
.swcrcfile can drive all your tooling (loader, Jest, CLI). - Active development – the Rust community keeps SWC up to date with the latest ECMAScript proposals and TypeScript features.
If you currently rely on esbuild only for transpilation (e.g., via esbuild-loader in webpack, esbuild-jest, or esbuild-register), the switch to SWC is straightforward and unlocks these benefits without sacrificing speed.
Step-by-Step Migration Guide
1. Replace esbuild-loader with swc-loader in Webpack
Remove esbuild-loader from your webpack configuration and install swc-loader and @swc/core:
npm install --save-dev @swc/core swc-loader
Then update your webpack.config.js rule for TypeScript/TSX files.
Before (esbuild-loader):
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2015',
},
},
},
],
},
};
After (swc-loader):
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
target: 'es2015',
},
module: {
type: 'es6', // or 'commonjs' depending on your output
},
},
},
},
],
},
};
You can extract the options into a shared .swcrc file and use configFile: true in the loader options to avoid duplication.
2. Migrate Jest Transformer from esbuild-jest to @swc/jest
Remove esbuild-jest and install @swc/jest and @swc/core:
npm install --save-dev @swc/core @swc/jest
Update your Jest configuration. If you previously used esbuild-jest as the transformer:
Before:
// jest.config.js
module.exports = {
transform: {
'^.+\\.tsx?$': 'esbuild-jest',
},
};
After:
// jest.config.js
module.exports = {
transform: {
'^.+\\.tsx?$': ['@swc/jest', {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
target: 'es2021',
},
module: {
type: 'commonjs', // Jest runs in Node.js CJS mode
},
}],
},
};
Alternatively, create a .swcrc at the project root and point Jest to it:
// jest.config.js
module.exports = {
transform: {
'^.+\\.tsx?$': ['@swc/jest', { configFile: true }],
},
};
Then populate .swcrc with the same options as above.
3. Switch Node.js Runtime Compilation from esbuild-register to @swc-node/register
If you use esbuild-register to run TypeScript files directly (e.g., scripts, servers, or development tools), replace it with @swc-node/register. Install the packages:
npm install --save-dev @swc-node/register @swc/core
Change your Node.js -r flag from esbuild-register to @swc-node/register. For example, in package.json:
Before:
"scripts": {
"start": "node -r esbuild-register src/index.ts"
}
After:
"scripts": {
"start": "node -r @swc-node/register src/index.ts"
}
The runtime hook reads a .swcrc file by default. Provide a configuration that matches your Node.js target:
// .swcrc
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": true
},
"target": "es2021",
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
}
},
"module": {
"type": "commonjs"
}
}
If you use ES modules, set "module.type": "es6" and run Node with --loader instead (consult @swc-node/register documentation).
4. Update Scripts and Configuration Files
Remove esbuild‑specific dependencies:
npm uninstall esbuild esbuild-loader esbuild-jest esbuild-register
Install the SWC equivalents:
npm install --save-dev @swc/core @swc/cli swc-loader @swc/jest @swc-node/register
If your project used esbuild CLI for transpilation (not bundling), you can replace it with swc CLI. For example, to compile a source directory to an output directory:
npx swc src -d dist --config-file .swcrc
If you relied on esbuild for bundling, note that SWC does not bundle by itself. You’ll need a dedicated bundler like webpack, Rollup, or the experimental swcpack. A common pattern is to use webpack with swc-loader as described in step 1. Update your package.json scripts accordingly, pointing the build step to webpack (or rollup) instead of esbuild.
5. Handle Custom Plugins and Edge Cases
esbuild plugins (e.g., for importing CSS, images, or applying custom transformations) do not have direct counterparts in SWC’s plugin system, which is still experimental and Rust‑based. You’ll need to replace these with bundler‑side solutions:
- For CSS or asset imports: use webpack’s
css-loader/style-loaderor Rollup plugins. - For define replacements (like
process.env.NODE_ENV): use webpack’sDefinePluginor Rollup’s@rollup/plugin-replace. - For externals: configure the
externalsfield in your webpack/rollup config.
SWC’s built‑in transforms cover most TypeScript and JSX needs. If you previously relied on esbuild plugins for non‑standard syntax, evaluate whether SWC’s native plugins (e.g., @swc/plugin-emotion) can replace them. Otherwise, fall back to Babel or bundler plugins for those specific transformations.
Best Practices for a Smooth Migration
- Migrate incrementally – start with the Jest transformer, then the webpack loader, and finally the runtime hook. This limits the blast radius if something breaks.
- Use a shared
.swcrc– define your SWC configuration once and reference it from Jest, webpack, and the CLI. This prevents mismatched settings. - Enable source maps – SWC supports inline and external source maps. Keep them enabled (
"sourceMaps": truein.swcrc) during migration to help debugging. - Run your full test suite after each step to catch regressions early.
- Keep type checking separate – SWC only transpiles; it does not check types. Continue using
tsc --noEmitin your CI or as a pre‑commit hook. - Verify performance – SWC is extremely fast, but always measure your actual build and test times before and after. The goal is to maintain or improve speed.
- Handle bundling explicitly – if esbuild was your sole bundler, introduce webpack or Rollup with SWC as the transpiler. Do not rely on the experimental
swcpackfor production unless you’ve thoroughly tested it. - Watch for Node.js version alignment – the
@swc-node/registerhook and thetargetin.swcrcshould match your Node.js runtime version to avoid unnecessary polyfills.
Conclusion
Migrating from esbuild to SWC opens the door to a Rust‑powered compilation pipeline that integrates seamlessly with modern JavaScript tooling. By replacing the esbuild‑based loader, Jest transformer, and Node runtime hook with their SWC equivalents, you gain a unified configuration model, broader plugin support, and native performance that scales with your project. Follow the incremental steps, lean on a shared .swcrc file, and keep your type checker running separately. The result is a faster, more maintainable build setup that aligns with the direction of the entire ecosystem.