SWC-powered transformer for Metro
- 🔧 Drop-in replacement for Metro Babel transform worker and minifier
- 🦀 All transformation in Rust, no Babel in sight
- 🏎️ Fast: transform worker is ~8× faster & full real world bundling ~3× faster
- ⚡️ Battery friendly: 15× less CPU utilization
- 🚇 Feature parity with Metro: HMR, inline requires,
Platform.select()substituion, constant folding, delta bundles etc - 🔤 Native support for Flow, TypeScript, ESM, CJS, JSX
- 🧵 Worklet/Reanimated support without Babel through custom SWC plugin
- 🔌 Support for SWC plugins and
process.envinlining - ⚛️ Expo Plugin for seamless integration
yarn add -D @react-native-swc/coreIf your app uses react-native-reanimated:
yarn add -D @react-native-swc/worklets-pluginAdd @react-native-swc/core to app.json:
{
"expo": {
"plugins": ["@react-native-swc/core"]
}
}npx expo prebuildThe plugin writes (or updates) a metro.config.js wired up to withSwcTransformer. If react-native-worklets is listed in your dependencies, the worklets SWC plugin is registered automatically. Any EXPO_PUBLIC_* env vars present at metro start are inlined into the bundle, matching Expo's default Babel pipeline. If you already have a manual withSwcTransformer call, the plugin leaves your config alone.
Disable worklet auto-detection if needed:
["@react-native-swc/core", { "worklets": false }]Note. Expo config plugins run only during
expo prebuild(andeas build, which invokes prebuild). Runningexpo startalone does not re-run plugins.
// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { withSwcTransformer } = require('@react-native-swc/core');
module.exports = withSwcTransformer(mergeConfig(getDefaultConfig(__dirname), {}));For Expo without the config plugin, swap @react-native/metro-config for expo/metro-config. To match Expo's default EXPO_PUBLIC_* env-var inlining, forward those vars through swcConfig.envs:
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withSwcTransformer } = require('@react-native-swc/core');
/** @type {import('@react-native-swc/core').SwcTransformerOptions} */
const swcConfig = {
envs: Object.fromEntries(
Object.entries(process.env).filter(([k, v]) => k.startsWith('EXPO_PUBLIC_')),
),
};
module.exports = withSwcTransformer(getDefaultConfig(__dirname), swcConfig);process.env.EXPO_PUBLIC_FOO references in your source are then replaced with the literal value at bundle time. Anything not prefixed with EXPO_PUBLIC_ is left alone.
// metro.config.js
const { getDefaultConfig } = require('@react-native/metro-config');
const { withSwcTransformer } = require('@react-native-swc/core');
/** @type {import('@react-native-swc/core').SwcTransformerOptions} */
const swcConfig = {
plugins: [
[
'@react-native-swc/worklets-plugin',
{
pluginVersion: require('react-native-worklets/package.json').version,
},
],
],
};
module.exports = withSwcTransformer(getDefaultConfig(__dirname), swcConfig);withSwcTransformer(metroConfig, swcOptions?) exposes an intentionally narrow surface — everything that affects Metro correctness is owned by the transform worker:
interface SwcTransformerOptions {
plugins?: ReadonlyArray<[string, Record<string, unknown>]>;
/**
* `process.env.FOO`-style replacements to inline at build time. Values are
* JSON-encoded for you and merged with the worker's built-ins (`NODE_ENV`,
* `EXPO_OS`, …). E.g. `{ API_URL: "https://x" }` replaces
* `process.env.API_URL` with the literal string `"https://x"`.
*/
envs?: Record<string, string>;
}- Custom Babel plugins from
babel.config.jsare not executed. Reanimated is covered by@react-native-swc/worklets-pluginin this repo, for other use cases see SWC plugin directory. - TypeScript sources must be isolatedModules-compatible. SWC parses each file in isolation.
- Flow handling is automatic. User
.jsfiles are parsed as Flow only if they carry an@flow/@noflowpragma or if they first fail to parse as plain JavaScript.
See CONTRIBUTING.md.
MIT.