mirror of
https://github.com/facebook/react-native.git
synced 2024-11-21 21:27:46 +00:00
Generate RCTThirdPartyComponentProvider (#47518)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/47518 This change reintroduce the generation of the `RCTThirdPartyComponentProvider` but in the right place and with the right patterns. 1. We are generating it in the user space, not in the node_modules (fixes the circular dependency) 2. We are not using weak function signature that have to be implicitly linked to some symbols found during compilation The change needs to crawl the folder to retrieve the information it needs. We need to implement it this way not to be breaking with respect of the current implementation. The assumption is that components have a function in their `.mm` file with this shape: ```objc Class<RCTComponentViewProtocol> <componentName>Cls(void) { return <ComponentViewClass>.class; } ``` I verified on GH that all the libraries out there follow this pattern. A better approach will let library owner to specify the association of `componentName, componentClass` in the `codegenConfig`. We will implement that as the next step and we will support both for some versions for backward compatibility. ## Changelog [iOS][Changed] - Change how components automatically register Reviewed By: dmytrorykun Differential Revision: D65614347 fbshipit-source-id: a378b8bc31c1ab3d49552f2f6a4c86c3b578746b
This commit is contained in:
parent
60b9d3d89e
commit
8becc2514d
@ -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>
|
||||
@ -235,7 +243,11 @@ using namespace facebook::react;
|
||||
|
||||
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
|
||||
{
|
||||
#if USE_OSS_CODEGEN
|
||||
return [RCTThirdPartyComponentsProvider thirdPartyFabricComponents];
|
||||
#else
|
||||
return @{};
|
||||
#endif
|
||||
}
|
||||
|
||||
- (RCTRootViewFactory *)createRCTRootViewFactory
|
||||
|
@ -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")
|
||||
|
@ -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": [],
|
||||
|
@ -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<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.
|
||||
//
|
||||
@ -764,6 +863,7 @@ function execute(projectRoot, targetPlatform, baseOutputPath) {
|
||||
platform,
|
||||
);
|
||||
|
||||
generateRCTThirdPartyComponents(libraries, outputPath);
|
||||
generateCustomURLHandlers(libraries, outputPath);
|
||||
|
||||
cleanupEmptyFilesAndFolders(outputPath);
|
||||
|
@ -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
|
@ -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
|
@ -149,7 +149,14 @@ static NSString *kBundlePath = @"js/RNTesterApp.ios";
|
||||
#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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user