Community CLI: Don't clobber user config of Metro resolver/serializer, expose CLI config to user as defaults (#51836)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51836

Currently, `community-cli-plugin` loads the user config and then overrides parts of it - specifically `resolver.resolveRequest` and `serializer.getModulesRunBeforeMainModule`, making it impossible for a user to modify this config.

Instead, treat the CLI's resolver and serializer customisations as "Framework defaults", layered on top of `react-native/metro-config` defaults but beneath a user's `metro.config.js`.

This allows the user to obtain (via `react-native/metro-config`'s `getDefaultConfig`) and extend or override them, if they need to.

This is technically breaking because users who currently have a custom `resolveRequest` or `getModulesRunBeforeMainModule` in their `metro.config.js` will have that config respected from this change, whereas currently they won't have any effect.

Changelog:
[General][Breaking] Community CLI users: user-defined `resolver.resolveRequest` and `serializer.getModulesRunBeforeMainModule` Metro config now takes precedence over CLI defaults

Reviewed By: huntie

Differential Revision: D74811395

fbshipit-source-id: c250caf798fdaedb0822bea3d6e65c0c3ae4d691
This commit is contained in:
Rob Hogan
2025-06-09 07:44:02 -07:00
committed by Facebook GitHub Bot
parent 85498ad46f
commit fe2bcbf4ba
3 changed files with 40 additions and 12 deletions
+2 -1
View File
@@ -35,7 +35,8 @@
"metro-resolver": "^0.82.4"
},
"peerDependencies": {
"@react-native-community/cli": "*"
"@react-native-community/cli": "*",
"@react-native/metro-config": "*"
},
"peerDependenciesMeta": {
"@react-native-community/cli": {
@@ -13,7 +13,7 @@ import type {ConfigT, InputConfigT, YargArguments} from 'metro-config';
import {CLIError} from './errors';
import {reactNativePlatformResolver} from './metroPlatformResolver';
import {loadConfig, mergeConfig, resolveConfig} from 'metro-config';
import {loadConfig, resolveConfig} from 'metro-config';
import path from 'path';
const debug = require('debug')('ReactNative:CommunityCliPlugin');
@@ -30,7 +30,7 @@ export type ConfigLoadingContext = $ReadOnly<{
/**
* Get the config options to override based on RN CLI inputs.
*/
function getOverrideConfig(
function getCommunityCliDefaultConfig(
ctx: ConfigLoadingContext,
config: ConfigT,
): InputConfigT {
@@ -85,6 +85,31 @@ export default async function loadMetroConfig(
ctx: ConfigLoadingContext,
options: YargArguments = {},
): Promise<ConfigT> {
let RNMetroConfig = null;
try {
RNMetroConfig = require('@react-native/metro-config');
} catch (e) {
throw new Error(
"Cannot resolve `@react-native/metro-config`. Ensure it is listed in your project's `devDependencies`.",
);
}
// Get the RN defaults before our customisations
const defaultConfig = RNMetroConfig.getDefaultConfig(ctx.root);
// Unflag the config as being loaded - it must be loaded again in userland.
global.__REACT_NATIVE_METRO_CONFIG_LOADED = false;
// Add our defaults to `@react-native/metro-config` before the user config
// loads them.
if (typeof RNMetroConfig.setFrameworkDefaults !== 'function') {
throw new Error(
'`@react-native/metro-config` does not have the expected API. Ensure it matches your React Native version.',
);
}
RNMetroConfig.setFrameworkDefaults(
getCommunityCliDefaultConfig(ctx, defaultConfig),
);
const cwd = ctx.root;
const projectConfig = await resolveConfig(options.config, cwd);
@@ -108,13 +133,8 @@ This warning will be removed in future (https://github.com/facebook/metro/issues
console.warn(line);
}
}
const config = await loadConfig({
return loadConfig({
cwd,
...options,
});
const overrideConfig = getOverrideConfig(ctx, config);
return mergeConfig(config, overrideConfig);
}
+10 -3
View File
@@ -8,7 +8,7 @@
* @format
*/
import type {ConfigT} from 'metro-config';
import type {ConfigT, InputConfigT} from 'metro-config';
import {getDefaultConfig as getBaseConfig, mergeConfig} from 'metro-config';
@@ -43,11 +43,16 @@ const INTERNAL_CALLSITES_REGEX = new RegExp(
export {mergeConfig} from 'metro-config';
let frameworkDefaults = {};
export function setFrameworkDefaults(config: InputConfigT) {
frameworkDefaults = config;
}
/**
* Get the base Metro configuration for a React Native project.
*/
export function getDefaultConfig(projectRoot: string): ConfigT {
const config = {
const reactNativeDefaults = {
resolver: {
resolverMainFields: ['react-native', 'browser', 'main'],
platforms: ['android', 'ios'],
@@ -99,5 +104,7 @@ export function getDefaultConfig(projectRoot: string): ConfigT {
// Set global hook so that the CLI can detect when this config has been loaded
global.__REACT_NATIVE_METRO_CONFIG_LOADED = true;
return mergeConfig(getBaseConfig.getDefaultValues(projectRoot), config);
const metroDefaults = getBaseConfig.getDefaultValues(projectRoot);
return mergeConfig(metroDefaults, reactNativeDefaults, frameworkDefaults);
}