From 3b93c3c4549f01474257b0836ff02db872ce55e0 Mon Sep 17 00:00:00 2001 From: Anukrati Agrawal Date: Tue, 28 Jul 2026 14:13:46 -0700 Subject: [PATCH] Fix cpp-lib example: dedupe react-native-windows in Metro config The NativeModuleSample/cpp-lib example rendered a blank screen because its Metro config loaded two copies of react-native-windows (one from the library root's node_modules and one from example/node_modules). Each copy had its own ReactNativeViewConfigRegistry, so the CircleMask view config registered by codegenNativeComponent('CircleMask') in one instance was looked up from the other, returning undefined and throwing: View config getter callback for component 'CircleMask' must be a function (received 'undefined'). which unmounted the whole tree (blank window; matches issue #15519). The existing dedupe used the deprecated 'blacklistRE' key (Metro 0.84 uses 'blockList') and built patterns with Windows backslashes that never matched Metro's forward-slash-normalized module paths, so it was a no-op. Fix: - Rename blacklistRE -> blockList and normalize path separators so the root node_modules duplicate of react-native-windows is actually blocked. - Add a resolveRequest hook that redirects react-native (and deep imports) to react-native-windows on the windows platform, ensuring a single renderer / view-config registry instance. Verified: dev bundle now contains a single ReactNativeViewConfigRegistry (bundle size 10.1 MB -> 5.09 MB) and the example renders fully, including the CircleMask native component. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cpp-lib/example/metro.config.js | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/samples/NativeModuleSample/cpp-lib/example/metro.config.js b/samples/NativeModuleSample/cpp-lib/example/metro.config.js index 5b7526c21..eb3d3b1df 100644 --- a/samples/NativeModuleSample/cpp-lib/example/metro.config.js +++ b/samples/NativeModuleSample/cpp-lib/example/metro.config.js @@ -27,18 +27,23 @@ const config = { // We need to make sure that only one version is loaded for peerDependencies // So we block them at the root, and alias them to the versions in example's node_modules resolver: { - blacklistRE: - modules.map( - (m) => - new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) - ).concat([ + blockList: + modules.map((m) => { + // Normalize separators so the pattern matches metro's forward-slash + // normalized module paths on Windows (path.join yields backslashes). + const escaped = escape(path.join(root, 'node_modules', m)).replace( + /\\\\/g, + '[\\\\/]' + ); + return new RegExp(`^${escaped}[\\\\/].*$`); + }).concat([ // This stops "npx @react-native-community/cli run-windows" from causing the metro server to crash if its already running new RegExp( `${path.resolve(__dirname, 'windows').replace(/[/\\]/g, '/')}.*`, ), // This prevents "npx @react-native-community/cli run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip or other files produced by msbuild - new RegExp(`${rnwPath}/build/.*`), - new RegExp(`${rnwPath}/target/.*`), + new RegExp(`${rnwPath.replace(/\\/g, '/')}/build/.*`), + new RegExp(`${rnwPath.replace(/\\/g, '/')}/target/.*`), /.*\.ProjectImports\.zip/, ]) , @@ -51,6 +56,44 @@ const config = { // } ), + + // On the windows platform, redirect 'react-native' (and its deep imports) + // to 'react-native-windows' so a single renderer / view-config registry + // instance is used. Without this, custom native components (e.g. those + // created via codegenNativeComponent) can be registered against one module + // instance and looked up from another, causing "View config getter callback + // for component `X` must be a function (received `undefined`)" at runtime. + resolveRequest: (context, moduleName, platform) => { + if (platform === 'windows') { + if (moduleName === 'react-native') { + return context.resolveRequest( + { ...context, resolveRequest: undefined }, + 'react-native-windows', + platform + ); + } + if (moduleName.startsWith('react-native/')) { + const redirected = moduleName.replace( + 'react-native/', + 'react-native-windows/' + ); + try { + return context.resolveRequest( + { ...context, resolveRequest: undefined }, + redirected, + platform + ); + } catch (e) { + // fall through to default resolution below + } + } + } + return context.resolveRequest( + { ...context, resolveRequest: undefined }, + moduleName, + platform + ); + }, }, transformer: {