Configuration system for customizing Metro behavior, transformers, resolvers, and build options. Metro supports project-specific configurations, environment-based settings, and extensive customization of the bundling pipeline.
Loads Metro configuration from various sources including config files, command line arguments, and defaults.
/**
* Load Metro configuration from multiple sources
* @param argv - Command line arguments and options
* @returns Promise resolving to complete Metro configuration
*/
function loadConfig(argv: Object): Promise<ConfigT>;
/**
* Resolve configuration file path
* @param configPath - Optional explicit config file path
* @returns Promise resolving to config file info or null
*/
function resolveConfig(configPath?: string): Promise<{
filepath: string;
isEmpty: boolean;
} | null>;Usage Example:
import { loadConfig, resolveConfig } from "metro";
// Load config with project root
const config = await loadConfig({
projectRoot: process.cwd(),
watchFolders: ["./packages"],
});
// Find config file
const configInfo = await resolveConfig();
if (configInfo) {
console.log("Using config:", configInfo.filepath);
}Combines user configuration with Metro defaults, allowing selective overrides.
/**
* Merge user configuration with Metro defaults
* @param defaultConfig - Base Metro configuration
* @param config - User configuration overrides
* @returns Merged configuration object
*/
function mergeConfig(
defaultConfig: ConfigT,
config: InputConfigT
): ConfigT;
/**
* Get default Metro configuration for a project
* @param projectRoot - Root directory of the project
* @returns Promise resolving to default configuration
*/
function getDefaultConfig(projectRoot: string): Promise<ConfigT>;Usage Example:
import { getDefaultConfig, mergeConfig } from "metro";
// Get defaults and customize
const defaultConfig = await getDefaultConfig(process.cwd());
const customConfig = mergeConfig(defaultConfig, {
transformer: {
babelTransformerPath: require.resolve("@react-native/metro-babel-transformer"),
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
resolver: {
alias: {
"@": "./src",
},
},
});interface ConfigT {
/** Caching configuration */
cacheStores: ReadonlyArray<CacheStore<TransformResultWithSource<>>>;
cacheVersion: string;
/** Project structure */
projectRoot: string;
watchFolders: ReadonlyArray<string>;
/** Resolver configuration */
resolver: ResolverConfig;
/** Transformer configuration */
transformer: TransformerConfig;
/** Serializer configuration */
serializer: SerializerConfig;
/** Server configuration */
server: ServerConfig;
/** Watcher configuration */
watcher: WatcherConfig;
/** Reporter for build events */
reporter: Reporter;
/** Maximum worker processes */
maxWorkers: number;
/** Reset cache on startup */
resetCache: boolean;
}
interface InputConfigT {
/** Project root directory */
projectRoot?: string;
/** Additional directories to watch */
watchFolders?: ReadonlyArray<string>;
/** Resolver configuration overrides */
resolver?: Partial<ResolverConfig>;
/** Transformer configuration overrides */
transformer?: Partial<TransformerConfig>;
/** Serializer configuration overrides */
serializer?: Partial<SerializerConfig>;
/** Server configuration overrides */
server?: Partial<ServerConfig>;
/** Watcher configuration overrides */
watcher?: Partial<WatcherConfig>;
/** Custom reporter */
reporter?: Reporter;
/** Maximum worker processes */
maxWorkers?: number;
/** Reset cache on startup */
resetCache?: boolean;
}interface ResolverConfig {
/** Alias mappings for module resolution */
alias: {[from: string]: string};
/** Alternative names for entry files */
aliasFields: ReadonlyArray<string>;
/** Asset file extensions */
assetExts: ReadonlyArray<string>;
/** Directories to search for modules */
blockList: RegExp | ReadonlyArray<RegExp>;
/** Custom dependency extractor */
dependencyExtractor?: string;
/** Enable symlink resolution */
enableSymlinks: boolean;
/** Node.js modules to exclude from bundle */
nodeModulesPaths: ReadonlyArray<string>;
/** Platforms to resolve */
platforms: ReadonlyArray<string>;
/** Source file extensions */
sourceExts: ReadonlyArray<string>;
/** Custom resolver function */
resolverMainFields: ReadonlyArray<string>;
/** Transform cache key */
hasteImplModulePath?: string;
}Usage Example:
const config = {
resolver: {
alias: {
"@components": "./src/components",
"@utils": "./src/utils",
},
assetExts: ["png", "jpg", "jpeg", "gif", "svg", "webp"],
sourceExts: ["js", "jsx", "ts", "tsx", "json"],
platforms: ["ios", "android", "web"],
nodeModulesPaths: ["./node_modules"],
},
};interface TransformerConfig {
/** Path to Babel transformer */
babelTransformerPath: string;
/** Transform options factory */
getTransformOptions: (
entryPoints: ReadonlyArray<string>,
options: {
dev: boolean;
hot: boolean;
minify: boolean;
platform?: string;
},
getDependenciesOf: (path: string) => Set<string>
) => Promise<TransformProfile>;
/** Additional Babel plugins */
transformVariants: TransformVariants;
/** Worker pool path */
workerPath: string;
/** Worker pool size */
maxWorkerCount: number;
/** Enable global cache */
globalPrefix: string;
}
interface TransformVariants {
/** Named transform profiles for different scenarios */
[name: string]: TransformProfile;
}
interface TransformProfile {
dev: boolean;
hot: boolean;
minify: boolean;
platform?: string;
transform: {
experimentalImportSupport: boolean;
inlineRequires: boolean;
nonInlinedRequires?: ReadonlyArray<string>;
unstable_allowRequireContext: boolean;
};
}Usage Example:
const config = {
transformer: {
babelTransformerPath: require.resolve("@react-native/metro-babel-transformer"),
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
unstable_allowRequireContext: true,
},
}),
},
};interface SerializerConfig {
/** Custom module ID factory */
createModuleIdFactory: () => (path: string) => number;
/** Custom serializer for bundle output */
customSerializer?: CustomSerializer;
/** Paths to exclude from bundle */
getModulesRunBeforeMainModule: (entryFilePath: string) => ReadonlyArray<string>;
/** Polyfills to include */
getPolyfills: () => ReadonlyArray<string>;
/** Runtime code to inject */
getRunModuleStatement: (moduleId: number | string) => string;
/** Post-process modules */
postProcessBundleSourcemap: PostProcessBundleSourcemap;
/** Process module filter */
processModuleFilter: (module: Module) => boolean;
}
type CustomSerializer = (
entryPoint: string,
preModules: ReadonlyArray<Module>,
graph: ReadOnlyGraph,
options: SerializerOptions
) => string | {code: string; map: string};interface ServerConfig {
/** Server port */
port: number;
/** Enable global hotkey support */
useGlobalHotkey: boolean;
/** Middleware enhancement function */
enhanceMiddleware?: (
middleware: Middleware,
server: MetroServer
) => Middleware;
/** Verify connections before processing */
verifyConnections: boolean;
}interface WatcherConfig {
/** Additional directories to watch */
additionalExts: ReadonlyArray<string>;
/** Watch mode implementation */
watchman: {
/** Defer initial scan */
defer_initial_scan: boolean;
};
/** Health check configuration */
healthCheck: {
/** Enable health checks */
enabled: boolean;
/** Health check interval */
interval: number;
/** Health check timeout */
timeout: number;
};
}Metro looks for configuration in several locations:
// metro.config.js
module.exports = {
projectRoot: __dirname,
watchFolders: ['./packages'],
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
resolver: {
alias: {
'@': './src',
},
},
};
// metro.config.json
{
"projectRoot": ".",
"watchFolders": ["./packages"],
"resolver": {
"alias": {
"@": "./src"
}
}
}
// package.json
{
"name": "my-app",
"metro": {
"resolver": {
"alias": {
"@": "./src"
}
}
}
}// metro.config.js
const baseConfig = {
projectRoot: __dirname,
};
if (process.env.NODE_ENV === 'production') {
module.exports = {
...baseConfig,
transformer: {
minifierPath: 'metro-minify-terser',
getTransformOptions: async () => ({
transform: {
inlineRequires: true,
},
}),
},
};
} else {
module.exports = {
...baseConfig,
transformer: {
getTransformOptions: async () => ({
transform: {
inlineRequires: false,
},
}),
},
};
}// Custom Babel transformer
module.exports = {
transformer: {
babelTransformerPath: require.resolve('./custom-transformer.js'),
},
};
// custom-transformer.js
const {transform} = require('@babel/core');
module.exports.transform = function({src, filename, options}) {
return transform(src, {
filename,
...options,
plugins: [
// Custom Babel plugins
require.resolve('./my-babel-plugin'),
],
});
};module.exports = {
resolver: {
resolverMainFields: ['react-native', 'browser', 'main'],
resolveRequest: (context, modulePath, platform) => {
// Custom resolution logic
if (modulePath.startsWith('virtual:')) {
return {
filePath: generateVirtualModule(modulePath),
type: 'sourceFile',
};
}
// Fall back to default resolution
return context.resolveRequest(context, modulePath, platform);
},
},
};module.exports = {
// Increase worker count for faster builds
maxWorkers: Math.max(require('os').cpus().length - 1, 1),
// Optimize transformer
transformer: {
getTransformOptions: async () => ({
transform: {
// Enable inline requires for smaller bundles
inlineRequires: true,
// Enable experimental import support
experimentalImportSupport: true,
},
}),
},
// Cache optimization
cacheStores: [
new FileStore({
root: path.join(os.tmpdir(), 'metro-cache'),
}),
],
};