docs/oss/migrating/babel-to-swc-migration.md
This document describes the migration from Babel to SWC for JavaScript/TypeScript transpilation in React on Rails projects using Shakapacker 9.0+.
SWC (Speedy Web Compiler) is a Rust-based JavaScript/TypeScript compiler that is approximately 20x faster than Babel. Shakapacker 9.0+ uses SWC as the default transpiler.
This guide assumes you're already using Shakapacker 9.0+. If you need to upgrade from an earlier version, see the Shakapacker upgrade guide.
Note: This migration has been successfully implemented in the React on Rails standard dummy app (react_on_rails/spec/dummy). The Pro dummy app (react_on_rails_pro/spec/dummy) continues using Babel for RSC stability.
yarn add -D @swc/core swc-loader
Change the javascript_transpiler setting from babel to swc:
default: &default # Using SWC for faster JavaScript transpilation (20x faster than Babel)
javascript_transpiler: swc
Create config/swc.config.js in your Rails application root with the following content:
let env;
try {
({ env } = require('shakapacker'));
} catch (error) {
console.error('Failed to load shakapacker:', error.message);
console.error('Make sure shakapacker is installed: yarn add shakapacker');
process.exit(1);
}
const customConfig = {
options: {
jsc: {
transform: {
react: {
runtime: 'automatic',
development: env.isDevelopment,
refresh: env.isDevelopment && env.runningWebpackDevServer,
useBuiltins: true,
},
},
// Keep class names for better debugging and compatibility
keepClassNames: true,
},
env: {
targets: '> 0.25%, not dead',
},
},
};
module.exports = customConfig;
After configuring SWC, test your build process:
# Compile assets
bundle exec rake shakapacker:compile
# Run tests
bundle exec rspec
Based on research and testing, here are the key findings regarding SWC and React Server Components compatibility:
'use client' / 'use server' boundary transform is performed by the react-on-rails-rsc WebpackLoader, which runs before babel/swc in the loader chain. RSC directive handling does not depend on your transpiler choice — SWC (or Babel) only does ordinary JS/TS transpilation of the RSC, server, and client bundles.swc-loader to build an RSC app is wired to work (the generated rscWebpackConfig.js extracts and chains either babel-loader or swc-loader) but is not yet verified end-to-end in React on Rails.Plugin Instability: All SWC plugins, including React-related ones, are considered experimental and may have breaking changes without semver guarantees
Framework Dependencies: React Server Components work best with frameworks that have explicit RSC support (like Next.js), as they require build-time infrastructure
Hydration Challenges: When using RSC with SWC, hydration mismatches can occur and are difficult to debug
Library Compatibility: Many popular React libraries are client-centric and may throw hydration errors when used in server components
If you need stable React Server Components support today:
javascript_transpiler: babel in shakapacker.yml| Babel Feature | SWC Equivalent | Notes |
|---|---|---|
| JSX Transform | jsc.transform.react | Automatic runtime supported |
| React Fast Refresh | jsc.transform.react.refresh | Works in development mode |
| Dynamic Imports | Shakapacker SWC parser default | Fully supported |
| Class Properties | Built-in | No config needed |
| TypeScript | Shakapacker SWC parser default | Native support |
| Babel Feature | SWC Approach | Migration Notes |
|---|---|---|
babel-plugin-transform-react-remove-prop-types | Built-in optimization | Handled automatically in production |
@babel/plugin-proposal-export-default-from | jsc.parser.exportDefaultFrom | Parser option instead of plugin |
| Babel macros | Not supported | Requires alternative implementation |
@loadable/babel-plugin | Manual code splitting | Use React.lazy() instead |
.swcrc files - Not recommended with webpack; use config/swc.config.js insteadBased on testing with React on Rails:
Solution: SWC automatically strips PropTypes in production mode. Ensure NODE_ENV=production is set.
Solution: CSS Modules handling is done by webpack, not by the transpiler. This should work the same with both Babel and SWC.
Solution: Enable decorators in SWC config:
jsc: {
parser: {
decorators: true;
}
}
Solution: Already configured with keepClassNames: true in our SWC config.
Solution: Clear node_modules and reinstall:
rm -rf node_modules yarn.lock
yarn install
Solution: Ensure webpack-dev-server is running and check that:
env.runningWebpackDevServer is true in developmentSolution: SWC parser is more permissive than Babel. Add TypeScript or stricter ESLint configuration for better error catching:
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
Solution: Do not hardcode jsc.parser in config/swc.config.js. Shakapacker selects the SWC parser per file extension, using TypeScript mode for .ts and .tsx files. Keep custom settings under jsc.transform, jsc.keepClassNames, and other non-parser options unless the app has a specific parser feature to enable.
All 305 RSpec tests pass successfully with SWC configuration:
305 examples, 0 failures
Test coverage includes:
For React on Rails projects without React Server Components: ✅ Migration to SWC is recommended
The standard React on Rails dummy app (react_on_rails/spec/dummy) successfully uses SWC, demonstrating its compatibility with core React on Rails features.
For projects using React Server Components: ⚠️ Stay with Babel for now - The React on Rails Pro dummy app builds RSC with Babel, which is the tested transpiler path. SWC-based RSC builds are wired to work but are not yet verified end-to-end, and the React Compiler's SWC plugin remains experimental. Stay with Babel, or conduct extensive testing before production deployment.