Skip to content

Generate RCTThirdPartyComponentProvider #47518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
#endif
#import <react/nativemodule/defaults/DefaultTurboModules.h>

#if __has_include(<ReactCodegen/RCTThirdPartyComponentsProvider.h>)
#define USE_OSS_CODEGEN 1
#import <ReactCodegen/RCTThirdPartyComponentsProvider.h>
#else
// Meta internal system do not generate the RCTModulesConformingToProtocolsProvider.h file
#define USE_OSS_CODEGEN 0
#endif

using namespace facebook::react;

@interface RCTAppDelegate () <RCTComponentViewFactoryComponentProvider, RCTHostDelegate>
Expand Down Expand Up @@ -235,7 +243,11 @@ - (Class)getModuleClassFromName:(const char *)name

- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
#if USE_OSS_CODEGEN
return [RCTThirdPartyComponentsProvider thirdPartyFabricComponents];
#else
return @{};
#endif
}

- (RCTRootViewFactory *)createRCTRootViewFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
// OSS-compatibility layer

#import <Foundation/Foundation.h>
#import <React/RCTThirdPartyFabricComponentsProvider.h>
#import <React/RCTComponentViewProtocol.h>

#pragma GCC diagnostic push
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
auto classFunc = p->second;
return classFunc();
}
return RCTThirdPartyFabricComponentsProvider(name);
return nullptr;
}

#endif // RN_DISABLE_OSS_PLUGIN_HEADER
3 changes: 2 additions & 1 deletion packages/react-native/scripts/cocoapods/codegen_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ def get_react_codegen_spec(package_json_file, folly_version: get_folly_config()[
'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": [],
Expand Down
184 changes: 123 additions & 61 deletions packages/react-native/scripts/codegen/generate-artifacts-executor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -573,27 +567,6 @@ function mustGenerateNativeCode(includeLibraryPath, schemaInfo) {
);
}

function createComponentProvider(schemas, supportedApplePlatforms) {
codegenLog('Creating component provider.', true);
const outputDir = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'React',
'Fabric',
);
fs.mkdirSync(outputDir, {recursive: true});
utils.getCodegen().generateFromSchemas(
{
schemas: schemas,
outputDirectory: outputDir,
supportedApplePlatforms,
},
{
generators: ['providerIOS'],
},
);
codegenLog(`Generated provider in: ${outputDir}`);
}

function findCodegenEnabledLibraries(pkgJson, projectRoot) {
const projectLibraries = findProjectRootLibraries(pkgJson, projectRoot);
if (pkgJsonIncludesGeneratedCode(pkgJson)) {
Expand Down Expand Up @@ -655,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<RCTComponentViewProtocol> '
// and ends with 'Cls(void)'. Return the string between the two.
function findRCTComponentViewProtocolClass(filepath) {
const fileContent = fs.readFileSync(filepath, 'utf8');
const regex = /Class<RCTComponentViewProtocol> (.*)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.
//
Expand Down Expand Up @@ -785,24 +863,8 @@ function execute(projectRoot, targetPlatform, baseOutputPath) {
platform,
);

if (
rootCodegenTargetNeedsThirdPartyComponentProvider(pkgJson, platform)
) {
const filteredSchemas = schemaInfos.filter(schemaInfo =>
dependencyNeedsThirdPartyComponentProvider(
schemaInfo,
platform,
pkgJson.codegenConfig?.name,
),
);
const schemas = filteredSchemas.map(schemaInfo => schemaInfo.schema);
const supportedApplePlatforms = filteredSchemas.map(
schemaInfo => schemaInfo.supportedApplePlatforms,
);

createComponentProvider(schemas, supportedApplePlatforms);
generateCustomURLHandlers(libraries, outputPath);
}
generateRCTThirdPartyComponents(libraries, outputPath);
generateCustomURLHandlers(libraries, outputPath);

cleanupEmptyFilesAndFolders(outputPath);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>

@protocol RCTComponentViewProtocol;

@interface RCTThirdPartyComponentsProvider: NSObject

+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;

@end
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>

#import "RCTThirdPartyComponentsProvider.h"
#import <React/RCTComponentViewProtocol.h>

@implementation RCTThirdPartyComponentsProvider

+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
return @{
{thirdPartyComponentsMapping}
};
}

@end
9 changes: 8 additions & 1 deletion packages/rn-tester/RNTester/AppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,14 @@ - (BOOL)bridgelessEnabled
#ifndef RN_DISABLE_OSS_PLUGIN_HEADER
- (nonnull NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)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

Expand Down
Loading