From 36cb9315d4d75c1966b07776a438b5abec0eef38 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Wed, 14 Oct 2026 23:04:17 +0300 Subject: [PATCH] fix(codegen): skip third-party components whose class is not linked Cherry-pick of react/react-native#59067 (acc0cca710), without its test file change: this suite has no generateRCTThirdPartyComponents case. The generated RCTThirdPartyComponentsProvider built its map from a dictionary literal of NSClassFromString values, so a component whose library the target does not link inserted nil and threw at launch. react-native-macos runs codegen with -t ios, so a library's iOS components reach a macOS target's provider whether or not the library ships macOS classes. The provider now maps each component name to its class name and skips a class that is not found, as RCTModuleProvidersMM.template does for modules. --- .../generate-artifacts-executor-test.js.snap | 38 ++++++++++++++++--- .../generateRCTThirdPartyComponents.js | 2 +- ...RCTThirdPartyComponentsProviderMM.template | 16 +++++++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap index 8c76f937ae04..d0ff08d48ce0 100644 --- a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap +++ b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap @@ -328,10 +328,24 @@ exports[`execute test-app "ReactCodegen/RCTThirdPartyComponentsProvider.mm" shou static dispatch_once_t nativeComponentsToken; dispatch_once(&nativeComponentsToken, ^{ - thirdPartyComponents = @{ - @\\"TestAppComponent\\": NSClassFromString(@\\"RCTTestAppComponent\\"), // test-app - @\\"TestLibraryComponent\\": NSClassFromString(@\\"RCTTestLibraryComponent\\"), // test-library + NSDictionary * componentMapping = @{ + @\\"TestAppComponent\\": @\\"RCTTestAppComponent\\", // test-app + @\\"TestLibraryComponent\\": @\\"RCTTestLibraryComponent\\", // test-library }; + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:componentMapping.count]; + + for (NSString *key in componentMapping) { + // Nil for a library this target does not link (an app extension, macOS). + Class klass = NSClassFromString(componentMapping[key]); + if (!klass) { + continue; + } + + [dict setObject:klass forKey:key]; + } + + thirdPartyComponents = dict; }); return thirdPartyComponents; @@ -817,9 +831,23 @@ exports[`execute test-app-legacy "ReactCodegen/RCTThirdPartyComponentsProvider.m static dispatch_once_t nativeComponentsToken; dispatch_once(&nativeComponentsToken, ^{ - thirdPartyComponents = @{ - @\\"TestAppDeprecatedComponent\\": NSClassFromString(@\\"RCTTestAppDeprecatedComponentClass\\"), // test-app-legacy + NSDictionary * componentMapping = @{ + @\\"TestAppDeprecatedComponent\\": @\\"RCTTestAppDeprecatedComponentClass\\", // test-app-legacy }; + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:componentMapping.count]; + + for (NSString *key in componentMapping) { + // Nil for a library this target does not link (an app extension, macOS). + Class klass = NSClassFromString(componentMapping[key]); + if (!klass) { + continue; + } + + [dict setObject:klass forKey:key]; + } + + thirdPartyComponents = dict; }); return thirdPartyComponents; diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/generateRCTThirdPartyComponents.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/generateRCTThirdPartyComponents.js index d54bd708c7b7..3b926b17a0a9 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor/generateRCTThirdPartyComponents.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/generateRCTThirdPartyComponents.js @@ -138,7 +138,7 @@ function generateRCTThirdPartyComponents( .flatMap(library => { const components = componentsInLibraries[library]; return components.map(({componentName, className}) => { - return `\t\t@"${componentName}": NSClassFromString(@"${className}"), // ${library}`; + return `\t\t@"${componentName}": @"${className}", // ${library}`; }); }) .join('\n'); diff --git a/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderMM.template b/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderMM.template index 4ce510709e33..6c50d6d9c98c 100644 --- a/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderMM.template +++ b/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderMM.template @@ -19,9 +19,23 @@ static dispatch_once_t nativeComponentsToken; dispatch_once(&nativeComponentsToken, ^{ - thirdPartyComponents = @{ + NSDictionary * componentMapping = @{ {thirdPartyComponentsMapping} }; + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:componentMapping.count]; + + for (NSString *key in componentMapping) { + // Nil for a library this target does not link (an app extension, macOS). + Class klass = NSClassFromString(componentMapping[key]); + if (!klass) { + continue; + } + + [dict setObject:klass forKey:key]; + } + + thirdPartyComponents = dict; }); return thirdPartyComponents;