Skip to content

Commit

Permalink
Generate code to allow for linking custom Module that conforms to som…
Browse files Browse the repository at this point in the history
…e protocols (#42923)

Summary:

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.

Differential Revision: D53441411
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Feb 9, 2024
1 parent cadaf26 commit 0ba404b
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 7 deletions.
61 changes: 54 additions & 7 deletions packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
// jsinspector-modern
#import <jsinspector-modern/InspectorFlags.h>

#import <React-Codegen/RCTModulesConformingToProtocolsProvider.h>

void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled)
{
RCTEnableTurboModule(turboModuleEnabled);
Expand Down Expand Up @@ -66,23 +68,68 @@ void RCTAppSetupPrepareApp(

id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(Class moduleClass)
{
// private block used to filter out modules depending on protocol conformance
NSArray * (^extractModuleConformingToProtocol)(NSArray<NSString *> *, RCTModuleRegistry *, Protocol *) =
^NSArray *(NSArray<NSString *> *classNames, RCTModuleRegistry *moduleRegistry, Protocol *protocol)
{
NSMutableArray *modules = [NSMutableArray new];

for (NSString *className in classNames) {
id clazz = NSClassFromString(className);

if (clazz == NULL) {
continue;
}

if (![clazz conformsToProtocol:protocol]) {
continue;
}

BOOL isModule =
[clazz conformsToProtocol:@protocol(RCTBridgeModule)] || [clazz conformsToProtocol:@protocol(RCTTurboModule)];
if (!isModule) {
continue;
}
const char *cModuleName = [className cStringUsingEncoding:NSUTF8StringEncoding];
[modules addObject:[moduleRegistry moduleForName:cModuleName]];
}
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] ];
// Extract valid RCTImageURLLoader
NSArray *imageURLLoaderModules = extractModuleConformingToProtocol(
[RCTModulesConformingToProtocolsProvider imageURLLoaderClassNames],
moduleRegistry,
@protocol(RCTImageURLLoader));

return [@[ [RCTBundleAssetImageLoader new] ] arrayByAddingObjectsFromArray:imageURLLoaderModules];
}
decodersProvider:^NSArray<id<RCTImageDataDecoder>> *(RCTModuleRegistry *moduleRegistry) {
return @[ [RCTGIFImageDecoder new] ];
// Extract valid RCTImageDataDecoder
NSArray *imageDataDecoder = extractModuleConformingToProtocol(
[RCTModulesConformingToProtocolsProvider imageDataDecoderClassNames],
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];
// Extract valid RCTURLRequestHandler
NSArray *URLRequestHandlerModules = extractModuleConformingToProtocol(
[RCTModulesConformingToProtocolsProvider URLRequestHandlerClassNames],
moduleRegistry,
@protocol(RCTURLRequestHandler));
return [@[
[RCTHTTPRequestHandler new],
[RCTDataRequestHandler new],
[RCTFileRequestHandler new],
[moduleRegistry moduleForName:"BlobModule"],
] arrayByAddingObjectsFromArray:URLRequestHandlerModules];
}];
}
// No custom initializer here.
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-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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -615,6 +677,8 @@ function execute(projectRoot, targetPlatform, baseOutputPath) {

createComponentProvider(schemas, supportedApplePlatforms);
}

generateCustomURLHandlers(libraries, outputPath);
cleanupEmptyFilesAndFolders(outputPath);
}
} catch (err) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 0ba404b

Please sign in to comment.