Steve Kinney

Speedy Web Compiler (SWC) for React Apps

If your React build times feel like they’re stuck in molasses, it might be time to meet SWC—the Rust-powered JavaScript compiler that’s making Babel look like it’s running on a potato. SWC (Speedy Web Compiler) transforms and bundles your JavaScript up to 20x faster than traditional tools, without sacrificing the transformations your modern React app needs. Whether you’re wrestling with sluggish development server startup times or production builds that take forever, SWC can help you get back to shipping features instead of waiting around.

What Even is SWC?

SWC is a next-generation JavaScript/TypeScript compiler written in Rust. Think of it as Babel’s caffeinated younger sibling—it does all the same transformations (JSX → JavaScript, modern ES6+ → browser-compatible code, TypeScript → JavaScript) but with the speed advantage that comes from being implemented in a systems programming language.

Unlike Babel, which is written in JavaScript and processes your code in a single-threaded manner, SWC leverages Rust’s performance characteristics and can utilize multiple CPU cores. The result? Compilation that’s not just faster, but dramatically faster—often 10-20x speed improvements for large codebases.

SWC excels at:

  1. JSX Transformation: Converting your React JSX syntax into regular JavaScript function calls
  2. TypeScript Compilation: Stripping types and transpiling TS to JS without the overhead of tsc
  3. Modern JavaScript: Transforming ES6+ features for older browser compatibility
  4. Minification: Code compression that rivals or beats Terser in both speed and output size
  5. Source Maps: Fast, accurate source map generation for debugging

Setting Up SWC with React

Let’s start with the most common scenario: integrating SWC into an existing React project. If you’re using a tool like Vite, you might already be using SWC under the hood without knowing it (Vite uses SWC for dependency pre-bundling). But let’s see how to configure it explicitly.

Installing SWC

First, let’s install the necessary SWC packages:

# Core SWC packages
npm install -D @swc/core @swc/cli

# If you're using webpack
npm install -D swc-loader

# If you're using TypeScript
npm install -D @swc/plugin-transform-imports

Basic SWC Configuration

SWC uses a .swcrc configuration file (similar to Babel’s .babelrc). Here’s a basic setup for a React TypeScript project:

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true,
      "decorators": false,
      "dynamicImport": true
    },
    "transform": {
      "react": {
        "runtime": "automatic",
        "development": true,
        "refresh": true
      }
    },
    "target": "es2022",
    "loose": false,
    "externalHelpers": false,
    "keepClassNames": false,
    "preserveAllComments": false
  },
  "module": {
    "type": "es6",
    "strict": false,
    "strictMode": true,
    "lazy": false,
    "noInterop": false
  },
  "minify": false,
  "isModule": true,
  "sourceMaps": true
}

Let’s break down the key parts:

  • jsc.parser.syntax: Tells SWC we’re processing TypeScript files
  • jsc.parser.tsx: Enables JSX parsing
  • jsc.transform.react.runtime: Uses React 17+‘s automatic JSX transform
  • jsc.transform.react.refresh: Enables React Fast Refresh for development
  • jsc.target: The JavaScript version to compile down to

Webpack Integration

If you’re using webpack (common in Create React App ejected projects or custom setups), you can replace babel-loader with swc-loader:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          // Replace babel-loader with swc-loader
          loader: 'swc-loader',
          options: {
            // SWC options here, or use .swcrc
            jsc: {
              parser: {
                syntax: 'typescript',
                tsx: true,
              },
              transform: {
                react: {
                  runtime: 'automatic',
                },
              },
              target: 'es2022',
            },
            module: {
              type: 'es6',
            },
          },
        },
      },
    ],
  },
};

React-Specific SWC Configuration

JSX Transform Options

SWC gives you fine-grained control over JSX transformation. Here are the key options for React:

{
  "jsc": {
    "transform": {
      "react": {
        "runtime": "automatic",
        "importSource": "@emotion/react",
        "pragma": "React.createElement",
        "pragmaFrag": "React.Fragment",
        "throwIfNamespace": true,
        "development": false,
        "useBuiltins": false,
        "refresh": false
      }
    }
  }
}

Key settings explained:

  • runtime: "automatic": Uses React 17+‘s new JSX transform (no need to import React)
  • runtime: "classic": Uses the old transform (requires React imports)
  • importSource: Custom JSX pragma source (useful for libraries like Emotion)
  • development: Enables development-time optimizations and better debugging
  • refresh: Enables React Fast Refresh for hot module replacement
Use different configurations for development vs. production by creating separate config files and switching between them based on

NODE_ENV.

Environment-Specific Configs

You can create separate SWC configs for different environments:

// .swcrc.development
{
  "jsc": {
    "transform": {
      "react": {
        "runtime": "automatic",
        "development": true,
        "refresh": true
      }
    },
    "target": "es2022"
  },
  "sourceMaps": "inline"
}
// .swcrc.production
{
  "jsc": {
    "transform": {
      "react": {
        "runtime": "automatic",
        "development": false,
        "refresh": false
      }
    },
    "target": "es2015"
  },
  "minify": true,
  "sourceMaps": true
}

Then switch between them in your build scripts:

{
  "scripts": {
    "dev": "SWCRC=.swcrc.development webpack serve",
    "build": "SWCRC=.swcrc.production webpack build"
  }
}

SWC vs. Babel: Performance Comparison

Let’s look at some Real World Numbers™. In a medium-sized React TypeScript project (~500 components, ~50k lines of code), here’s what you might expect:

TaskBabelSWCSpeed Improvement
Initial compilation45s3.2s~14x faster
Hot reload (single file)800ms45ms~18x faster
Production build2m 15s8.5s~16x faster
TypeScript type-checking*N/A12sN/A

*SWC can strip TypeScript types but doesn’t do type-checking. You’ll still need tsc --noEmit for that.

Your mileage will vary based on project size, complexity, and hardware. Larger projects tend to see even more dramatic improvements.

Advanced SWC Features for React

Custom Import Transformations

One killer feature is SWC’s ability to transform imports, similar to babel-plugin-import. This is especially useful for tree-shaking large UI libraries:

{
  "jsc": {
    "experimental": {
      "plugins": [
        [
          "@swc/plugin-transform-imports",
          {
            "lodash": {
              "transform": "lodash/{{member}}",
              "preventFullImport": true
            },
            "@mui/material": {
              "transform": "@mui/material/{{member}}",
              "preventFullImport": true
            }
          }
        ]
      ]
    }
  }
}

This transforms:

// ❌ This imports the entire library
import { Button, TextField } from '@mui/material';

// ✅ SWC transforms it to this
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';

Source Map Generation

SWC generates source maps that are both fast and accurate. For development, you might want inline source maps:

{
  "sourceMaps": "inline",
  "inlineSourcesContent": true
}

For production, external source maps are usually better:

{
  "sourceMaps": true,
  "sourceFileName": "index.js",
  "sourceRoot": "/src"
}

Minification with SWC

SWC includes a built-in minifier that’s faster than Terser and produces similarly compact output:

{
  "minify": true,
  "jsc": {
    "minify": {
      "compress": {
        "unused": true,
        "dead_code": true,
        "drop_console": true,
        "drop_debugger": true,
        "pure_funcs": ["console.log", "console.info"]
      },
      "mangle": {
        "keepClassName": false,
        "keepFnName": false,
        "keepPrivateProps": false,
        "reserved": []
      }
    }
  }
}

Real-World Integration Examples

Next.js with SWC

Next.js 12+ uses SWC by default, but you can customize it:

// next.config.js
module.exports = {
  experimental: {
    swcMinify: true, // Use SWC for minification
  },
  swcMinify: true,
  compiler: {
    // React strict mode
    reactStrictMode: true,

    // Remove console.log in production
    removeConsole: process.env.NODE_ENV === 'production',

    // Custom JSX import source
    emotion: {
      sourceMap: true,
      autoLabel: 'dev-only',
      labelFormat: '[local]',
      cssPropOptimization: true,
    },
  },
};

Vite with SWC

Vite can use SWC instead of esbuild for transformations:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';

export default defineConfig({
  plugins: [react()],
  esbuild: false, // Disable esbuild to use SWC
});

Custom Webpack + SWC Setup

For a from-scratch webpack setup optimized for React:

// webpack.config.js
const path = require('path');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';

  return {
    entry: './src/index.tsx',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: isProduction ? '[name].[contenthash].js' : '[name].js',
    },
    module: {
      rules: [
        {
          test: /\.(ts|tsx|js|jsx)$/,
          exclude: /node_modules/,
          use: {
            loader: 'swc-loader',
            options: {
              jsc: {
                parser: {
                  syntax: 'typescript',
                  tsx: true,
                },
                transform: {
                  react: {
                    runtime: 'automatic',
                    development: !isProduction,
                    refresh: !isProduction,
                  },
                },
                target: isProduction ? 'es2015' : 'es2022',
              },
              module: {
                type: 'es6',
              },
              minify: isProduction,
              sourceMaps: true,
            },
          },
        },
      ],
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.js', '.jsx'],
    },
  };
};

Common Gotchas and Tradeoffs

TypeScript Type Checking

SWC is blazingly fast at stripping TypeScript types, but it doesn’t actually check them. You’ll need to run tsc --noEmit separately:

{
  "scripts": {
    "type-check": "tsc --noEmit",
    "build": "npm run type-check && swc src -d dist"
  }
}
Don’t skip type checking entirely—runtime errors from type issues are much harder to debug than compile-time ones.

Plugin Ecosystem

Babel has a massive plugin ecosystem that SWC doesn’t fully match yet. Some specific transformations you rely on might not have SWC equivalents. However, the core transformations (JSX, TypeScript, ES6+) are solid.

Bundle Analysis

Tools like webpack-bundle-analyzer work great with SWC-compiled code, but make sure source maps are enabled for accurate analysis:

npm install -D webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
    }),
  ],
};

Memory Usage

SWC generally uses less memory than Babel, but very large projects might still hit memory limits. You can tune the worker count:

{
  "jsc": {
    "experimental": {
      "cacheRoot": ".swc"
    }
  }
}

Migration Strategy

Gradual Migration from Babel

You don’t have to switch everything at once. Here’s a pragmatic approach:

  1. Start with development builds: Use SWC for faster dev server startup
  2. Test thoroughly: Ensure your app works identically with SWC
  3. Switch production builds: Once you’re confident, use SWC for production
  4. Monitor bundle sizes: Verify that minification produces similar results

Testing Your Migration

Create a simple test to verify your SWC setup works:

// test-swc.ts
import React from 'react';

interface Props {
  name: string;
  count?: number;
}

const TestComponent: React.FC<Props> = ({ name, count = 0 }) => {
  return (
    <div>
      <h1>Hello {name}!</h1>
      <p>Count: {count}</p>
    </div>
  );
};

export default TestComponent;

Compile it with SWC:

npx swc test-swc.ts -o test-swc.js

The output should be clean, properly transformed JavaScript.

When NOT to Use SWC

While SWC is excellent for most React projects, there are some scenarios where sticking with Babel might make sense:

  • Heavy plugin dependencies: If your build relies on specific Babel plugins that don’t have SWC equivalents
  • Experimental JavaScript features: Babel sometimes supports cutting-edge proposals before SWC
  • Legacy project constraints: If you’re in a complex monorepo setup where changing the build tool would require extensive coordination

Next Steps

Last modified on .