How to Migrate Babel Projects to SWC: Configuration Differences and Compatibility Guide

Migrating from Babel to SWC requires replacing .babelrc or babel.config.js with a .swcrc JSON file, mapping presets to SWC's env and react fields, and selecting the appropriate rootMode to match Babel's configuration resolution behavior.

SWC (Speedy Web Compiler) is a Rust-based TypeScript and JavaScript compiler maintained in the swc-project/swc repository that serves as a drop-in replacement for Babel. While both tools transpile modern JavaScript to older targets, they differ significantly in configuration schema, plugin architecture, and project root resolution. Understanding these differences is essential for preserving build behavior while gaining SWC's performance benefits.

Configuration File Location and Resolution

Babel and SWC use different strategies for locating and loading configuration files.

Babel searches for babel.config.js (project-wide) or .babelrc (per-directory) relative to the file being compiled, merging multiple .babelrc files when traversing directories.

SWC uses .swcrc (JSON format) and implements three distinct root modes that control how the compiler walks the file system:

  • upward (default): Searches upward from the source file and stops at the first .swcrc. Errors if none is found.
  • upward-optional: Searches upward but silently uses defaults if no config exists.
  • downward: Mimics Babel's cascading behavior by allowing multiple configs to merge when traversing directories.

The resolution logic is implemented in crates/swc/src/config/mod.rs (lines 740-770), while the CLI entry point that wires this together lives in crates/swc_cli_impl/src/commands/compile.rs.

Mapping Babel Presets and Plugins to SWC

The configuration schemas differ in structure and terminology. In Babel, you declare presets and plugins as arrays of strings; in SWC, equivalent features are nested under typed fields in the jsc object.

Key mappings:

  • @babel/preset-env → env field with targets object
  • @babel/preset-react → jsc.transform.react configuration
  • @babel/preset-typescript → jsc.parser.syntax: "typescript"
  • Parser plugins (JSX, dynamic imports, decorators) → jsc.parser boolean flags
  • Class properties / decorators → jsc.transform.legacyDecorator and jsc.transform.decoratorMetadata

The complete TypeScript definitions for these fields are exported from packages/types/index.ts, which serves as the source of truth for valid configuration options.

Parser and Transform Options

Enable specific ECMAScript features under the jsc section:

{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": true,
      "dynamicImport": true,
      "decorators": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true,
      "react": {
        "runtime": "automatic",
        "development": false
      }
    }
  }
}

Unlike Babel's plugin-based model, SWC implements transforms as built-in Rust modules. Custom plugins must be written as native Rust or WebAssembly modules using the API defined in crates/swc_plugin.

Compatibility Limitations and Gaps

Not all Babel functionality has direct SWC equivalents. Plan for these gaps before migrating:

  • Flow typing: SWC parses Flow syntax but does not emit Flow-specific transforms. Projects relying on Flow compilation must retain Babel for those files.
  • Experimental proposals: SWC only implements proposals that have reached the ECMAScript specification (e.g., class properties, private methods). Stage 3 proposals require waiting for SWC support or maintaining Babel for those specific inputs.
  • Config merging: Babel automatically merges multiple .babelrc files when traversing subdirectories. To replicate this behavior in SWC, you must set "rootMode": "downward" in your .swcrc.
  • Source maps: Both tools support sourceMaps: true|inline|both, but SWC generates maps by default unless explicitly disabled. The generation logic resides in crates/swc_ecma_codegen/src/lib.rs.

Step-by-Step Migration Checklist

Follow these steps to transition your build pipeline while preserving semantic parity:

  1. Install dependencies: Run npm i -D @swc/cli @swc/core to add the compiler and command-line interface.

  2. Create .swcrc: Translate your Babel configuration into the SWC JSON schema, mapping presets to their equivalent fields.

  3. Select root mode: If you previously used multiple .babelrc files for nested overrides, set "rootMode": "downward" to enable cascading config resolution.

  4. Update build scripts: Replace babel src --out-dir lib --extensions ".js,.jsx" with swc src -d lib --config-file .swcrc.

  5. Verify output: Use the swc_estree_compat crate to compare ASTs. Run the comparison script in crates/swc_estree_compat/tests/compare.sh to diff Babel and SWC outputs.

  6. Handle unsupported plugins: Identify Babel plugins without SWC equivalents and either retain Babel for those file patterns or implement a custom Rust/WebAssembly plugin.

Practical Configuration Examples

Minimal .swcrc for React/TypeScript Projects

This configuration mirrors a typical Babel setup with React, TypeScript, and environment-specific targets:

{
  "jsc": {
    "target": "es2017",
    "parser": {
      "syntax": "typescript",
      "tsx": true,
      "dynamicImport": true
    },
    "transform": {
      "react": {
        "runtime": "automatic",
        "refresh": false
      }
    }
  },
  "module": {
    "type": "commonjs"
  },
  "env": {
    "targets": {
      "chrome": "80",
      "node": "14"
    }
  },
  "sourceMaps": true,
  "rootMode": "upward"
}

CLI Usage Comparison

Migrate your build scripts by replacing Babel commands with SWC equivalents:


# Original Babel command

npx babel src --out-dir lib --extensions ".js,.jsx,.ts,.tsx" --source-maps

# SWC equivalent

npx swc src -d lib --config-file .swcrc

The --config-file flag explicitly points to your configuration; if omitted, SWC automatically searches for .swcrc using the resolution algorithm in crates/swc/src/config/mod.rs.

Programmatic API Migration

Update Node.js build scripts to use the @swc/core API, which mirrors Babel's synchronous transform signature:

const { transformSync } = require("@swc/core");

// Previous Babel usage:
// const result = babel.transformSync(code, { presets: ["@babel/preset-react"] });

const result = transformSync(code, {
  jsc: {
    parser: { syntax: "ecmascript", jsx: true },
    target: "es2020",
    transform: {
      react: { runtime: "automatic" }
    }
  },
  sourceMaps: true
});

Verifying AST Compatibility

Validate semantic equivalence by converting SWC's AST to Babel's format and comparing outputs:

cd crates/swc_estree_compat
./tests/compare.sh path/to/your/file.js

This script uses the conversion logic in crates/swc_estree_compat/src/convert.rs to generate a side-by-side diff, ensuring your migration preserves runtime behavior.

Summary

  • Replace configuration files: Move from .babelrc or babel.config.js to a single .swcrc JSON file located at your project root.
  • Map presets carefully: Translate @babel/preset-env to the env field, @babel/preset-react to jsc.transform.react, and enable TypeScript via jsc.parser.syntax.
  • Control resolution behavior: Use "rootMode": "downward" to replicate Babel's cascading config merging, or stick with "upward" for isolated directory-specific configs.
  • Account for plugin gaps: Flow transforms and Stage 3 experimental proposals require retaining Babel or writing custom Rust/WebAssembly plugins.
  • Verify before shipping: Use the swc_estree_compat comparison tool to validate AST equivalence between Babel and SWC outputs.

Frequently Asked Questions

Can I use my existing babel.config.js with SWC?

No. SWC requires a .swcrc JSON file and does not execute JavaScript configuration files. You must manually translate your Babel presets and plugins into the equivalent SWC JSON schema, using the type definitions in packages/types/index.ts as a reference for available options.

How do I handle Babel plugins that don't exist in SWC?

For plugins without native SWC equivalents, you have three options: retain Babel for specific file patterns using a build tool that supports multiple loaders, wait for community implementation of the transform, or write a custom plugin using the Rust or WebAssembly plugin API defined in crates/swc_plugin.

Does SWC support Flow type annotations?

SWC can parse Flow syntax without errors, but it does not strip Flow types or perform Flow-specific transforms during compilation. Projects using Flow must either continue using Babel for type stripping or use a separate tool like flow-remove-types in their build pipeline.

Is SWC faster than Babel for all project sizes?

Yes. According to the swc-project/swc benchmarks, the Rust-based compiler is typically 2-5× faster than Babel for large codebases, with particularly significant improvements in projects containing extensive TypeScript or JSX transformation pipelines. The performance advantage comes from SWC's parallelized Rust implementation and lack of plugin startup overhead.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →