Metro
React Native uses Metro to build your JavaScript code and assets.
Configuring Metro
Configuration options for Metro can be customized in your project's metro.config.js file. This can export either:
- An object (recommended) that will be merged on top of Metro's internal config defaults.
- A function that will be called with Metro's internal config defaults and should return a final config object.
Please see Configuring Metro on the Metro website for documentation on all available config options.
In React Native, your Metro config should extend @react-native/metro-config, which contains essential defaults necessary to build and run React Native apps. On desktop, the config comes from makeMetroConfig in @react-native-gpui/core/metro.config, which extends it with the desktop platform, as shown below.
Below is the desktop/metro.config.js file of a new react-native-gpui project:
const path = require('path');
const { makeMetroConfig } = require('@react-native-gpui/core/metro.config');
module.exports = makeMetroConfig({
projectRoot: path.resolve(__dirname, '..'),
});
makeMetroConfig returns a complete Metro config. It:
- bundles for the
desktopplatform, and resolves.desktop.jsfiles first, then.native.js, then plain files; - falls back to a module's
.ios.jsfile when a library ships no desktop or native file; - replaces the files of React Native, and of libraries that declare
sourceOverrides, with their desktop versions; - prefers a package's
mainfield overbrowser, so packages resolve to their native bindings instead of a WebAssembly build; - watches the folders that linked packages and workspaces live in;
- applies the transform that the active JavaScript engine needs.
To customize the config, merge your own options into the result:
const path = require('path');
const { mergeConfig } = require('@react-native/metro-config');
const { makeMetroConfig } = require('@react-native-gpui/core/metro.config');
const config = {
transformer: {
getTransformOptions: async () => ({ transform: { inlineRequires: true } }),
},
};
module.exports = mergeConfig(
makeMetroConfig({ projectRoot: path.resolve(__dirname, '..') }),
config,
);
Metro options you wish to customize can be done so within the config object.
Advanced: Using a config function
Exporting a config function is an opt-in to managing the final config yourself — Metro will not apply any internal defaults. This pattern can be useful when needing to read the base default config object from Metro or to set options dynamically.
From @react-native/metro-config 0.72.1, it is no longer necessary to use a config function to access the complete default config. See the Tip section below.
const {
getDefaultConfig,
mergeConfig,
} = require('@react-native/metro-config');
module.exports = function (baseConfig) {
const defaultConfig = mergeConfig(
baseConfig,
getDefaultConfig(__dirname),
);
const {
resolver: {assetExts, sourceExts},
} = defaultConfig;
return mergeConfig(defaultConfig, {
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'],
},
});
};
Using a config function is for advanced use cases. A simpler method than the above, e.g. for customising sourceExts, would be to read these defaults from @react-native/metro-config.
Alternative
const defaultConfig = getDefaultConfig(__dirname);
const config = {
resolver: {
sourceExts: [...defaultConfig.resolver.sourceExts, 'svg'],
},
};
module.exports = mergeConfig(defaultConfig, config);
However!, we recommend copying and editing when overriding these config values — placing the source of truth in your config file.
✅ Recommended
const config = {
resolver: {
sourceExts: ['js', 'ts', 'tsx', 'svg'],
},
};