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.

Reviewed By: RSNara

Differential Revision: D53441411
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Feb 13, 2024
1 parent 0575033 commit fe65af3
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,17 @@
// 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);
Expand Down Expand Up @@ -66,23 +77,59 @@ void RCTAppSetupPrepareApp(

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.
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 fe65af3

Please sign in to comment.