One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Metro Mode

Opting into Metro for native builds

Overview

One uses Rolldown for native builds by default. Metro is available when you select it explicitly.

When to Use Metro Mode

The Rolldown-based native pipeline is the recommended default. It provides fast dev builds with HMR via React Refresh and production builds with tree shaking. Metro remains available for apps that need it.

Enabling Metro Mode

Configure the native bundler to Metro in your vite.config.ts:

Install the Expo Babel preset in the app first:

Terminal window
bun add -d babel-preset-expo

One reports an installation error when Metro starts without it. The default Rolldown path does not load the preset.

vite.config.ts
import type { UserConfig } from 'vite'
import { one } from 'one/vite'
export default {
plugins: [
one({
native: {
// Enable Metro bundler for native builds
bundler: 'metro',
},
}),
],
} satisfies UserConfig

Limitations

When using Metro mode, only some of the Vite features are supported. One provides only a subset of Vite functionality in Metro mode to maintain compatibility and stability.

Metro mode intentionally supports only a subset of Vite configurations and features. This approach helps avoid implementation differences between Vite and Metro that could cause compatibility issues with React Native packages.
We are actively working on expanding Vite feature support, but for any features we don’t yet provide through Vite configuration, you can always use the standard React Native way to change the configuration using metro.config.js.

Only the following Vite features and configurations are supported in Metro mode for now:

Configuration

By default, One starts from React Native’s Metro configuration and applies the same One router, environment, and server transforms used by the native Rolldown path.

We recommend using the standard Vite way for configuring if possible, as it provides a more consistent experience across web and native builds, and will be more future-proof to switch between bundlers.

Like ordinary React Native apps, you can still configure Metro using metro.config.js for more advanced settings, such as resolver or watcher options:

metro.config.js
const { withOne } = require('one/metro-config')
module.exports = withOne(__dirname)

metro.config.js works exactly as it would in a standard React Native project, giving you full control over the bundling process.

No Babel

By default, One uses its own Metro worker built around Oxc. The worker combines Oxc’s Rust parser and transforms with TypeScript implementations of One’s native transforms, Hermes parsing for Flow, and esbuild for syntax lowering. This pipeline runs without Babel.

A Babel plugin One has no port for would silently do nothing, so instead the build fails and names it. You have three options:

A native transform receives source text and returns updated source text. It can use Oxc or another tool suited to the transformation:

scripts/my-transform.cjs
module.exports = function myTransform(code, ctx) {
// ctx: { filename, platform, dev, projectRoot }
if (!code.includes('__BUILD_ID')) return null
return code.replace(/__BUILD_ID/g, JSON.stringify(process.env.BUILD_ID))
}
vite.config.ts
one({
native: {
bundler: 'metro',
bundlerOptions: {
nativeTransformModules: ['./scripts/my-transform.cjs'],
},
},
})

Return null to leave the file alone. Transforms run in order, before One’s built-in transforms. The first receives the original file contents; each subsequent transform receives the previous transform’s output. A transform that records original source positions should run first.

Lazy Startup

By default, Metro starts as soon as the Vite dev server is ready. If you’re primarily doing web development but have Metro configured, you can defer Metro startup until a native client actually connects:

vite.config.ts
import type { UserConfig } from 'vite'
import { one } from 'one/vite'
export default {
plugins: [
one({
native: {
bundler: 'metro',
bundlerOptions: {
startup: 'lazy',
},
},
}),
],
} satisfies UserConfig

Or via environment variable:

Terminal window
ONE_METRO_LAZY=1 one dev

With lazy startup:

This can significantly speed up dev server startup when you don’t always need native builds.

Performance

Metro can use more mature React Native tooling. One’s default Rolldown path remains the recommended choice for new apps. Both native bundlers use Oxc by default; Metro can opt back into Babel.

Edit this page on GitHub.