Generate code to allow for linking custom Module that conforms to some protocols (#42923)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/42923

This Diff implement the logic to:
- Read some lists of class names provided by libraries that conforms to some protocols we defined as extension points.
- Generate a provider in the React-Codegen podspec, whose code lives alongside the app code.
- Glue the app and the generated code together, allowing to link custom protocols

## Changelog
[iOS][Added] - Allow libraries to provide module which conforms to protocols meant to be extension points.

Reviewed By: RSNara

Differential Revision: D53441411

fbshipit-source-id: f53bc6ea0417e6122d8918df2614bcb9937a515a
This commit is contained in:
Riccardo Cipolleschi
2024-02-13 06:18:54 -08:00
committed by Facebook GitHub Bot
parent f9a5b30e5a
commit 46d7f8a669
5 changed files with 173 additions and 7 deletions
@@ -24,6 +24,20 @@
#import <React/RCTFabricSurface.h>
#import <React/RCTSurfaceHostingProxyRootView.h>
// jsinspector-modern
#import <jsinspector-modern/InspectorFlags.h>
#if __has_include(<React-Codegen/RCTModulesConformingToProtocolsProvider.h>)
#define USE_OSS_CODEGEN 1
#import <React-Codegen/RCTModulesConformingToProtocolsProvider.h>
#elif __has_include(<React_Codegen/RCTModulesConformingToProtocolsProvider.h>)
#define USE_OSS_CODEGEN 1
#import <React_Codegen/RCTModulesConformingToProtocolsProvider.h>
#else
// Meta internal system do not generate the RCTModulesConformingToProtocolsProvider.h file
#define USE_OSS_CODEGEN 0
#endif
void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled)
{
RCTEnableTurboModule(turboModuleEnabled);
@@ -49,23 +63,59 @@ RCTAppSetupDefaultRootView(RCTBridge *bridge, NSString *moduleName, NSDictionary
id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(Class moduleClass)
{
// private block used to filter out modules depending on protocol conformance
NSArray * (^extractModuleConformingToProtocol)(RCTModuleRegistry *, Protocol *) =
^NSArray *(RCTModuleRegistry *moduleRegistry, Protocol *protocol)
{
NSArray<NSString *> *classNames = @[];
#if USE_OSS_CODEGEN
if (protocol == @protocol(RCTImageURLLoader)) {
classNames = [RCTModulesConformingToProtocolsProvider imageURLLoaderClassNames];
} else if (protocol == @protocol(RCTImageDataDecoder)) {
classNames = [RCTModulesConformingToProtocolsProvider imageDataDecoderClassNames];
} else if (protocol == @protocol(RCTURLRequestHandler)) {
classNames = [RCTModulesConformingToProtocolsProvider URLRequestHandlerClassNames];
}
#endif
NSMutableArray *modules = [NSMutableArray new];
for (NSString *className in classNames) {
const char *cModuleName = [className cStringUsingEncoding:NSUTF8StringEncoding];
id moduleFromLibrary = [moduleRegistry moduleForName:cModuleName];
if (![moduleFromLibrary conformsToProtocol:protocol]) {
continue;
}
[modules addObject:moduleFromLibrary];
}
return modules;
};
// Set up the default RCTImageLoader and RCTNetworking modules.
if (moduleClass == RCTImageLoader.class) {
return [[moduleClass alloc] initWithRedirectDelegate:nil
loadersProvider:^NSArray<id<RCTImageURLLoader>> *(RCTModuleRegistry *moduleRegistry) {
return @[ [RCTBundleAssetImageLoader new] ];
NSArray *imageURLLoaderModules =
extractModuleConformingToProtocol(moduleRegistry, @protocol(RCTImageURLLoader));
return [@[ [RCTBundleAssetImageLoader new] ] arrayByAddingObjectsFromArray:imageURLLoaderModules];
}
decodersProvider:^NSArray<id<RCTImageDataDecoder>> *(RCTModuleRegistry *moduleRegistry) {
return @[ [RCTGIFImageDecoder new] ];
NSArray *imageDataDecoder = extractModuleConformingToProtocol(moduleRegistry, @protocol(RCTImageDataDecoder));
return [@[ [RCTGIFImageDecoder new] ] arrayByAddingObjectsFromArray:imageDataDecoder];
}];
} else if (moduleClass == RCTNetworking.class) {
return [[moduleClass alloc]
initWithHandlersProvider:^NSArray<id<RCTURLRequestHandler>> *(RCTModuleRegistry *moduleRegistry) {
return [NSArray arrayWithObjects:[RCTHTTPRequestHandler new],
[RCTDataRequestHandler new],
[RCTFileRequestHandler new],
[moduleRegistry moduleForName:"BlobModule"],
nil];
NSArray *URLRequestHandlerModules =
extractModuleConformingToProtocol(moduleRegistry, @protocol(RCTURLRequestHandler));
return [@[
[RCTHTTPRequestHandler new],
[RCTDataRequestHandler new],
[RCTFileRequestHandler new],
[moduleRegistry moduleForName:"BlobModule"],
] arrayByAddingObjectsFromArray:URLRequestHandlerModules];
}];
}
// No custom initializer here.
@@ -76,6 +76,7 @@ Pod::Spec.new do |s|
s.dependency "React-RCTImage"
s.dependency "React-CoreModules"
s.dependency "React-nativeconfig"
s.dependency "React-Codegen"
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-NativeModulesApple")
@@ -60,6 +60,22 @@ const CORE_LIBRARIES_WITH_OUTPUT_FOLDER = {
};
const REACT_NATIVE = 'react-native';
const MODULES_PROTOCOLS_H_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTModulesConformingToProtocolsProviderH.template',
);
const MODULES_PROTOCOLS_MM_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTModulesConformingToProtocolsProviderMM.template',
);
// HELPERS
function pkgJsonIncludesGeneratedCode(pkgJson) {
@@ -503,6 +519,52 @@ function findCodegenEnabledLibraries(pkgJson, projectRoot) {
}
}
function generateCustomURLHandlers(libraries, outputDir) {
const customImageURLLoaderClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTImageURLLoader,
)
.filter(Boolean)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const customImageDataDecoderClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTImageDataDecoder,
)
.filter(Boolean)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const customURLHandlerClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTURLRequestHandler,
)
.filter(Boolean)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const template = fs.readFileSync(MODULES_PROTOCOLS_MM_TEMPLATE_PATH, 'utf8');
const finalMMFile = template
.replace(/{imageURLLoaderClassNames}/, customImageURLLoaderClasses)
.replace(/{imageDataDecoderClassNames}/, customImageDataDecoderClasses)
.replace(/{requestHandlersClassNames}/, customURLHandlerClasses);
fs.writeFileSync(
path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.mm'),
finalMMFile,
);
const templateH = fs.readFileSync(MODULES_PROTOCOLS_H_TEMPLATE_PATH, 'utf8');
fs.writeFileSync(
path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.h'),
templateH,
);
}
// It removes all the empty files and empty folders
// it finds, starting from `filepath`, recursively.
//
@@ -615,6 +677,8 @@ function execute(projectRoot, targetPlatform, baseOutputPath) {
createComponentProvider(schemas, supportedApplePlatforms);
}
generateCustomURLHandlers(libraries, outputPath);
cleanupEmptyFilesAndFolders(outputPath);
}
} catch (err) {
@@ -0,0 +1,18 @@
/*
* 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>
@interface RCTModulesConformingToProtocolsProvider: NSObject
+(NSArray<NSString *> *)imageURLLoaderClassNames;
+(NSArray<NSString *> *)imageDataDecoderClassNames;
+(NSArray<NSString *> *)URLRequestHandlerClassNames;
@end
@@ -0,0 +1,33 @@
/*
* 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 "RCTModulesConformingToProtocolsProvider.h"
@implementation RCTModulesConformingToProtocolsProvider
+(NSArray<NSString *> *)imageURLLoaderClassNames
{
return @[
{imageURLLoaderClassNames}
];
}
+(NSArray<NSString *> *)imageDataDecoderClassNames
{
return @[
{imageDataDecoderClassNames}
];
}
+(NSArray<NSString *> *)URLRequestHandlerClassNames
{
return @[
{requestHandlersClassNames}
];
}
@end