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 (facebook#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 f94788e
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 f94788e

Please sign in to comment.