diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index 8a020c142a3..1d6e0e31eea 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -34,6 +34,14 @@ #endif #import +#if __has_include() +#define USE_OSS_CODEGEN 1 +#import +#else +// Meta internal system do not generate the RCTModulesConformingToProtocolsProvider.h file +#define USE_OSS_CODEGEN 0 +#endif + using namespace facebook::react; @interface RCTAppDelegate () @@ -235,7 +243,11 @@ using namespace facebook::react; - (NSDictionary> *)thirdPartyFabricComponents { +#if USE_OSS_CODEGEN + return [RCTThirdPartyComponentsProvider thirdPartyFabricComponents]; +#else return @{}; +#endif } - (RCTRootViewFactory *)createRCTRootViewFactory diff --git a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec index edaf73d1f06..729a7a0bb6a 100644 --- a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec +++ b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec @@ -76,6 +76,7 @@ Pod::Spec.new do |s| s.dependency "React-nativeconfig" s.dependency "React-RCTFBReactNativeSpec" s.dependency "React-defaultsnativemodule" + s.dependency "ReactCodegen" add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"]) add_dependency(s, "React-NativeModulesApple") diff --git a/packages/react-native/scripts/cocoapods/codegen_utils.rb b/packages/react-native/scripts/cocoapods/codegen_utils.rb index 3c2aca4f058..067184cd61d 100644 --- a/packages/react-native/scripts/cocoapods/codegen_utils.rb +++ b/packages/react-native/scripts/cocoapods/codegen_utils.rb @@ -121,7 +121,8 @@ class CodegenUtils 'source_files' => "**/*.{h,mm,cpp}", 'pod_target_xcconfig' => { "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), - "FRAMEWORK_SEARCH_PATHS" => framework_search_paths + "FRAMEWORK_SEARCH_PATHS" => framework_search_paths, + "OTHER_CPLUSPLUSFLAGS" => "$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}", }, 'dependencies': { "React-jsiexecutor": [], diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor.js b/packages/react-native/scripts/codegen/generate-artifacts-executor.js index 83eff1b9430..bd2f24b5c41 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor.js @@ -86,6 +86,22 @@ const MODULES_PROTOCOLS_MM_TEMPLATE_PATH = path.join( 'RCTModulesConformingToProtocolsProviderMM.template', ); +const THIRD_PARTY_COMPONENTS_H_TEMPLATE_PATH = path.join( + REACT_NATIVE_PACKAGE_ROOT_FOLDER, + 'scripts', + 'codegen', + 'templates', + 'RCTThirdPartyComponentsProviderH.template', +); + +const THIRD_PARTY_COMPONENTS_MM_TEMPLATE_PATH = path.join( + REACT_NATIVE_PACKAGE_ROOT_FOLDER, + 'scripts', + 'codegen', + 'templates', + 'RCTThirdPartyComponentsProviderMM.template', +); + const codegenLog = (text, info = false) => { // ANSI escape codes for colors and formatting const reset = '\x1b[0m'; @@ -541,28 +557,6 @@ function generateNativeCode( }); } -function rootCodegenTargetNeedsThirdPartyComponentProvider(pkgJson, platform) { - return !pkgJsonIncludesGeneratedCode(pkgJson) && platform === 'ios'; -} - -function dependencyNeedsThirdPartyComponentProvider( - schemaInfo, - platform, - appCodegenConfigSpec, -) { - // Filter the react native core library out. - // In the future, core library and third party library should - // use the same way to generate/register the fabric components. - // We also have to filter out the the components defined in the app - // because the RCTThirdPartyComponentProvider is generated inside Fabric, - // which lives in a different target from the app and it has no visibility over - // the symbols defined in the app. - return ( - !isReactNativeCoreLibrary(schemaInfo.library.config.name, platform) && - schemaInfo.library.config.name !== appCodegenConfigSpec - ); -} - function mustGenerateNativeCode(includeLibraryPath, schemaInfo) { // If library's 'codegenConfig' sets 'includesGeneratedCode' to 'true', // then we assume that native code is shipped with the library, @@ -634,6 +628,111 @@ function generateCustomURLHandlers(libraries, outputDir) { ); } +function generateRCTThirdPartyComponents(libraries, outputDir) { + fs.mkdirSync(outputDir, {recursive: true}); + // Generate Header File + codegenLog('Generating RCTThirdPartyComponentsProvider.h'); + const templateH = fs.readFileSync( + THIRD_PARTY_COMPONENTS_H_TEMPLATE_PATH, + 'utf8', + ); + const finalPathH = path.join(outputDir, 'RCTThirdPartyComponentsProvider.h'); + fs.writeFileSync(finalPathH, templateH); + codegenLog(`Generated artifact: ${finalPathH}`); + + codegenLog('Generating RCTThirdPartyComponentsProvider.mm'); + let componentsInLibraries = {}; + libraries.forEach(({config, libraryPath}) => { + if (isReactNativeCoreLibrary(config.name) || config.type === 'modules') { + return; + } + const libraryName = JSON.parse( + fs.readFileSync(path.join(libraryPath, 'package.json')), + ).name; + codegenLog(`Crawling ${libraryName} library for components`); + // crawl all files and subdirectories for file with the ".mm" extension + const files = findFilesWithExtension(libraryPath, '.mm'); + + componentsInLibraries[libraryName] = files + .flatMap(file => findRCTComponentViewProtocolClass(file)) + .filter(Boolean); + }); + + const thirdPartyComponentsMapping = Object.keys(componentsInLibraries) + .flatMap(library => { + const components = componentsInLibraries[library]; + return components.map(({componentName, className}) => { + return `\t\t@"${componentName}": NSClassFromString(@"${className}"), // ${library}`; + }); + }) + .join('\n'); + // Generate implementation file + const templateMM = fs + .readFileSync(THIRD_PARTY_COMPONENTS_MM_TEMPLATE_PATH, 'utf8') + .replace(/{thirdPartyComponentsMapping}/, thirdPartyComponentsMapping); + const finalPathMM = path.join( + outputDir, + 'RCTThirdPartyComponentsProvider.mm', + ); + fs.writeFileSync(finalPathMM, templateMM); + codegenLog(`Generated artifact: ${finalPathMM}`); +} + +// Given a path, return the paths of all the files with extension .mm in +// the path dir and all its subdirectories. +function findFilesWithExtension(filePath, extension) { + const files = []; + const dir = fs.readdirSync(filePath); + dir.forEach(file => { + const absolutePath = path.join(filePath, file); + if ( + fs.existsSync(absolutePath) && + fs.statSync(absolutePath).isDirectory() + ) { + files.push(...findFilesWithExtension(absolutePath, extension)); + } else if (file.endsWith(extension)) { + files.push(absolutePath); + } + }); + return files; +} + +// Given a filepath, read the file and look for a string that starts with 'Class ' +// and ends with 'Cls(void)'. Return the string between the two. +function findRCTComponentViewProtocolClass(filepath) { + const fileContent = fs.readFileSync(filepath, 'utf8'); + const regex = /Class (.*)Cls\(/; + const match = fileContent.match(regex); + if (match) { + const componentName = match[1]; + + // split the file by \n + // remove all the lines before the one that matches the regex above + // find the first return statement after that that ends with .class + // return what's between return and `.class` + const lines = fileContent.split('\n'); + const signatureIndex = lines.findIndex(line => regex.test(line)); + const returnRegex = /return (.*)\.class/; + const classNameMatch = String(lines.slice(signatureIndex)).match( + returnRegex, + ); + if (classNameMatch) { + const className = classNameMatch[1]; + codegenLog(`Match found ${componentName} -> ${className}`); + return { + componentName, + className, + }; + } + + console.warn( + `Could not find class name for component ${componentName}. Register it manually`, + ); + return null; + } + return null; +} + // It removes all the empty files and empty folders // it finds, starting from `filepath`, recursively. // @@ -764,6 +863,7 @@ function execute(projectRoot, targetPlatform, baseOutputPath) { platform, ); + generateRCTThirdPartyComponents(libraries, outputPath); generateCustomURLHandlers(libraries, outputPath); cleanupEmptyFilesAndFolders(outputPath); diff --git a/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderH.template b/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderH.template new file mode 100644 index 00000000000..ab1a249de35 --- /dev/null +++ b/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderH.template @@ -0,0 +1,16 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@protocol RCTComponentViewProtocol; + +@interface RCTThirdPartyComponentsProvider: NSObject + ++ (NSDictionary> *)thirdPartyFabricComponents; + +@end diff --git a/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderMM.template b/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderMM.template new file mode 100644 index 00000000000..34f84bc5c6e --- /dev/null +++ b/packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderMM.template @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + +#import + +#import "RCTThirdPartyComponentsProvider.h" +#import + +@implementation RCTThirdPartyComponentsProvider + ++ (NSDictionary> *)thirdPartyFabricComponents +{ + return @{ +{thirdPartyComponentsMapping} + }; +} + +@end diff --git a/packages/rn-tester/RNTester/AppDelegate.mm b/packages/rn-tester/RNTester/AppDelegate.mm index eacc13101b2..cff5e645688 100644 --- a/packages/rn-tester/RNTester/AppDelegate.mm +++ b/packages/rn-tester/RNTester/AppDelegate.mm @@ -149,7 +149,14 @@ static NSString *kBundlePath = @"js/RNTesterApp.ios"; #ifndef RN_DISABLE_OSS_PLUGIN_HEADER - (nonnull NSDictionary> *)thirdPartyFabricComponents { - return @{@"RNTMyNativeView" : RNTMyNativeViewComponentView.class}; +#if USE_OSS_CODEGEN + return [super thirdPartyFabricComponents].mutableCopy; +#else + NSMutableDictionary *dict = [super thirdPartyFabricComponents].mutableCopy; + dict[@"RNTMyNativeView"] = NSClassFromString(@"RNTMyNativeViewComponentView"); + dict[@"SampleNativeComponent"] = NSClassFromString(@"RCTSampleNativeComponentComponentView"); + return dict; +#endif } #endif