One supports using Metro as the bundler for native builds, providing a more stable and battle-tested bundling experience. The default native bundler now uses Rolldown’s experimental dev() API (alpha), while Metro mode offers production-ready stability by leveraging React Native’s standard bundler.
Configure the native bundler to Metro in your 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 UserConfigWhen 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.config.js.Only the following Vite features and configurations are supported in Metro mode for now:
import.meta.env Built-in Constants Partially Supported
: supported but may not work as expected for web specific variables.import.meta.env Env Variables Supported
tsconfig-paths Partially Supported
: As one automatically configuresvite-tsconfig-paths to support path mappings defined in tsconfig.json for your project, the same applies to Metro mode. However, if you are using the vite-tsconfig-paths plugin manually, the custom options you provide there will not be applied for Metro.By default, the Metro bundler is configured with Expo’s defaults.
Like ordinary React Native apps, you can still configure Metro using metro.config.js for more advanced settings, such as resolver or watcher options:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
module.exports = config;metro.config.js works exactly as it would in a standard React Native project, giving you full control over the bundling process.
By default, One replaces Expo’s Babel transformer with its own 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; it is not a standalone Rust bundler. babel.config.js and babel-preset-expo do not configure this worker.
A Babel plugin One has no port for would silently do nothing, so instead the build fails and names it. You have three options:
native.bundlerOptions.nativeTransformModules. Each entry is a module id (Metro runs transforms in separate worker processes, so it has to be serializable) that default-exports (code, ctx) => string | null. Your transforms run first, on the original source, with an absolute ctx.filename.native.bundlerOptions.babelConfigOverrides.native.bundlerOptions.nativeTransforms: false to go back to the Babel transformer, or ONE_METRO_NATIVE_TRANSFORMS=0 for a single run.A native transform receives source text and returns updated source text. It can use Oxc or another tool suited to the transformation:
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))}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.
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:
import type { UserConfig } from 'vite'import { one } from 'one/vite'
export default { plugins: [ one({ native: { bundler: 'metro', bundlerOptions: { startup: 'lazy', }, }, }), ],} satisfies UserConfigOr via environment variable:
ONE_METRO_LAZY=1 one devWith lazy startup:
one dev/status, it gets an immediate response while Metro starts in the backgroundThis can significantly speed up dev server startup when you don’t always need native builds.
Metro mode is designed for production stability, but it may not match the performance of Vite, which bundles with Rolldown. Both paths transform with oxc and run no Babel. We observed that Metro mode provides better memory efficiency.
Edit this page on GitHub.