Skip to content

Commit 6312360

Browse files
cipolleschifacebook-github-bot
authored andcommitted
Improve Codegen to support automatic registration of Cxx TM (facebook#49624)
Summary: Pull Request resolved: facebook#49624 This change improves the iOS infra so that there is no need to modify the Swift AppDelegate or to create a Bridging Header. ## Problem As of today, it is not possible to create a pure C++ TM and to register it through a Swift AppDelegate ## Solution We can create a pod that can be imported in a Swift AppDelegate and that offer some pure Objective-C classes. These classes contains a provider that can be instantiated in Swift. The TurboModule manager delegate will ask the AppDelegate about the presence of some provider that can instantiate a pure C++ turbomodule with a given name. The provider has an empty interface, but the implementation contains a function that can actually instantiate the TM. The function is implemented in an Objective-C++ class that imports the pure C++ turbomodule and creates it. The TMManager extends the provider through a category to attaach the signature of the function that is implemented by the provider. The last diff in this stack contains an exaple on how to implement this. ## Changelog: [iOS][Added] - Wire codegen to the new TM provider to automatically register CXX modules. Reviewed By: javache Differential Revision: D70082999 fbshipit-source-id: 11d829450e1d17984d6f22ee5b8907073c59d008
1 parent 5d5d370 commit 6312360

File tree

6 files changed

+162
-32
lines changed

6 files changed

+162
-32
lines changed

packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,8 @@ - (void)hostDidStart:(RCTHost *)host
9292

9393
- (nullable id<RCTModuleProvider>)getModuleProvider:(const char *)name
9494
{
95-
// NSString *providerName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
96-
// return self.dependencyProvider ? self.dependencyProvider.moduleProviders[providerName] : nullptr;
97-
return nil;
95+
NSString *providerName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
96+
return self.dependencyProvider ? self.dependencyProvider.moduleProviders[providerName] : nullptr;
9897
}
9998

10099
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name

packages/react-native/Libraries/AppDelegate/RCTDependencyProvider.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#import <Foundation/Foundation.h>
99

1010
@protocol RCTComponentViewProtocol;
11+
@protocol RCTModuleProvider;
1112

1213
NS_ASSUME_NONNULL_BEGIN
1314

@@ -21,6 +22,8 @@ NS_ASSUME_NONNULL_BEGIN
2122

2223
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;
2324

25+
- (nonnull NSDictionary<NSString *, id<RCTModuleProvider>> *)moduleProviders;
26+
2427
@end
2528

2629
NS_ASSUME_NONNULL_END

packages/react-native/scripts/codegen/generate-artifacts-executor.js

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -70,67 +70,60 @@ const packageJsonPath = path.join(
7070
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
7171
const REACT_NATIVE = packageJson.name;
7272

73-
const MODULES_PROTOCOLS_H_TEMPLATE_PATH = path.join(
73+
const TEMPLATES_FOLDER_PATH = path.join(
7474
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
7575
'scripts',
7676
'codegen',
7777
'templates',
78+
);
79+
80+
const MODULES_PROTOCOLS_H_TEMPLATE_PATH = path.join(
81+
TEMPLATES_FOLDER_PATH,
7882
'RCTModulesConformingToProtocolsProviderH.template',
7983
);
8084

8185
const MODULES_PROTOCOLS_MM_TEMPLATE_PATH = path.join(
82-
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
83-
'scripts',
84-
'codegen',
85-
'templates',
86+
TEMPLATES_FOLDER_PATH,
8687
'RCTModulesConformingToProtocolsProviderMM.template',
8788
);
8889

8990
const THIRD_PARTY_COMPONENTS_H_TEMPLATE_PATH = path.join(
90-
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
91-
'scripts',
92-
'codegen',
93-
'templates',
91+
TEMPLATES_FOLDER_PATH,
9492
'RCTThirdPartyComponentsProviderH.template',
9593
);
9694

9795
const THIRD_PARTY_COMPONENTS_MM_TEMPLATE_PATH = path.join(
98-
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
99-
'scripts',
100-
'codegen',
101-
'templates',
96+
TEMPLATES_FOLDER_PATH,
10297
'RCTThirdPartyComponentsProviderMM.template',
10398
);
10499

100+
const MODULE_PROVIDERS_H_TEMPLATE_PATH = path.join(
101+
TEMPLATES_FOLDER_PATH,
102+
'RCTModuleProvidersH.template',
103+
);
104+
105+
const MODULE_PROVIDERS_MM_TEMPLATE_PATH = path.join(
106+
TEMPLATES_FOLDER_PATH,
107+
'RCTModuleProvidersMM.template',
108+
);
109+
105110
const APP_DEPENDENCY_PROVIDER_H_TEMPLATE_PATH = path.join(
106-
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
107-
'scripts',
108-
'codegen',
109-
'templates',
111+
TEMPLATES_FOLDER_PATH,
110112
'RCTAppDependencyProviderH.template',
111113
);
112114

113115
const APP_DEPENDENCY_PROVIDER_MM_TEMPLATE_PATH = path.join(
114-
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
115-
'scripts',
116-
'codegen',
117-
'templates',
116+
TEMPLATES_FOLDER_PATH,
118117
'RCTAppDependencyProviderMM.template',
119118
);
120119

121120
const APP_DEPENDENCY_PROVIDER_PODSPEC_TEMPLATE_PATH = path.join(
122-
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
123-
'scripts',
124-
'codegen',
125-
'templates',
121+
TEMPLATES_FOLDER_PATH,
126122
'ReactAppDependencyProvider.podspec.template',
127123
);
128124

129125
const REACT_CODEGEN_PODSPEC_TEMPLATE_PATH = path.join(
130-
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
131-
'scripts',
132-
'codegen',
133-
'templates',
126+
TEMPLATES_FOLDER_PATH,
134127
'ReactCodegen.podspec.template',
135128
);
136129

@@ -693,6 +686,70 @@ function generateAppDependencyProvider(outputDir) {
693686
codegenLog(`Generated podspec: ${finalPathPodspec}`);
694687
}
695688

689+
function generateRCTModuleProviders(
690+
projectRoot,
691+
pkgJson,
692+
libraries,
693+
outputDir,
694+
) {
695+
fs.mkdirSync(outputDir, {recursive: true});
696+
// Generate Header File
697+
codegenLog('Generating RCTModulesProvider.h');
698+
const templateH = fs.readFileSync(MODULE_PROVIDERS_H_TEMPLATE_PATH, 'utf8');
699+
const finalPathH = path.join(outputDir, 'RCTModuleProviders.h');
700+
fs.writeFileSync(finalPathH, templateH);
701+
codegenLog(`Generated artifact: ${finalPathH}`);
702+
703+
codegenLog('Generating RCTModuleProviders.mm');
704+
let modulesInLibraries = {};
705+
706+
let app = pkgJson.codegenConfig
707+
? {config: pkgJson.codegenConfig, libraryPath: projectRoot}
708+
: null;
709+
libraries
710+
.concat(app)
711+
.filter(Boolean)
712+
.forEach(({config, libraryPath}) => {
713+
if (
714+
isReactNativeCoreLibrary(config.name) ||
715+
config.type === 'components'
716+
) {
717+
return;
718+
}
719+
720+
const libraryName = JSON.parse(
721+
fs.readFileSync(path.join(libraryPath, 'package.json')),
722+
).name;
723+
if (config.ios?.modulesProvider) {
724+
modulesInLibraries[libraryName] = Object.keys(
725+
config.ios?.modulesProvider,
726+
).map(moduleName => {
727+
return {
728+
moduleName,
729+
className: config.ios?.modulesProvider[moduleName],
730+
};
731+
});
732+
}
733+
});
734+
735+
const modulesMapping = Object.keys(modulesInLibraries)
736+
.flatMap(library => {
737+
const modules = modulesInLibraries[library];
738+
return modules.map(({moduleName, className}) => {
739+
return `\t\t@"${moduleName}": @"${className}", // ${library}`;
740+
});
741+
})
742+
.join('\n');
743+
744+
// Generate implementation file
745+
const templateMM = fs
746+
.readFileSync(MODULE_PROVIDERS_MM_TEMPLATE_PATH, 'utf8')
747+
.replace(/{moduleMapping}/, modulesMapping);
748+
const finalPathMM = path.join(outputDir, 'RCTModuleProviders.mm');
749+
fs.writeFileSync(finalPathMM, templateMM);
750+
codegenLog(`Generated artifact: ${finalPathMM}`);
751+
}
752+
696753
function generateRCTThirdPartyComponents(libraries, outputDir) {
697754
fs.mkdirSync(outputDir, {recursive: true});
698755
// Generate Header File
@@ -1027,6 +1084,7 @@ function execute(projectRoot, targetPlatform, baseOutputPath, source) {
10271084
if (source === 'app') {
10281085
// These components are only required by apps, not by libraries
10291086
generateRCTThirdPartyComponents(libraries, outputPath);
1087+
generateRCTModuleProviders(projectRoot, pkgJson, libraries, outputPath);
10301088
generateCustomURLHandlers(libraries, outputPath);
10311089
generateAppDependencyProvider(outputPath);
10321090
}

packages/react-native/scripts/codegen/templates/RCTAppDependencyProviderMM.template

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
#import "RCTAppDependencyProvider.h"
99
#import <ReactCodegen/RCTModulesConformingToProtocolsProvider.h>
1010
#import <ReactCodegen/RCTThirdPartyComponentsProvider.h>
11+
#import <ReactCodegen/RCTModuleProviders.h>
1112

1213
@implementation RCTAppDependencyProvider {
1314
NSArray<NSString *> * _URLRequestHandlerClassNames;
1415
NSArray<NSString *> * _imageDataDecoderClassNames;
1516
NSArray<NSString *> * _imageURLLoaderClassNames;
1617
NSDictionary<NSString *,Class<RCTComponentViewProtocol>> * _thirdPartyFabricComponents;
18+
NSDictionary<NSString *, id<RCTModuleProvider>> * _moduleProviders;
1719
}
1820

1921
- (nonnull NSArray<NSString *> *)URLRequestHandlerClassNames {
@@ -52,4 +54,12 @@
5254
return _thirdPartyFabricComponents;
5355
}
5456

57+
- (nonnull NSDictionary<NSString *, id<RCTModuleProvider>> *)moduleProviders {
58+
static dispatch_once_t modulesToken;
59+
dispatch_once(&modulesToken, ^{
60+
_moduleProviders = RCTModuleProviders.moduleProviders;
61+
});
62+
return _moduleProviders;
63+
}
64+
5565
@end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <Foundation/Foundation.h>
9+
10+
@protocol RCTModuleProvider;
11+
12+
@interface RCTModuleProviders: NSObject
13+
14+
+ (NSDictionary<NSString *, id<RCTModuleProvider>> *)moduleProviders;
15+
16+
@end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <Foundation/Foundation.h>
9+
10+
#import "RCTModuleProviders.h"
11+
#import <ReactCommon/RCTTurboModule.h>
12+
#import <React/RCTLog.h>
13+
14+
@implementation RCTModuleProviders
15+
16+
+ (NSDictionary<NSString *, id<RCTModuleProvider>> *)moduleProviders
17+
{
18+
NSDictionary<NSString *, NSString *> * moduleMapping = @{
19+
{moduleMapping}
20+
};
21+
22+
NSMutableDictionary *dict = [NSMutableDictionary new];
23+
24+
for (NSString *key in moduleMapping) {
25+
NSString * moduleProviderName = moduleMapping[key];
26+
Class klass = NSClassFromString(moduleProviderName);
27+
if (!klass) {
28+
RCTLogError(@"Module provider %@ cannot be found in the runtime", moduleProviderName);
29+
continue;
30+
}
31+
32+
id instance = [klass new];
33+
if (![instance respondsToSelector:@selector(getTurboModule:)]) {
34+
RCTLogError(@"Module provider %@ does not conform to RCTModuleProvider", moduleProviderName);
35+
continue;
36+
}
37+
38+
[dict setObject:instance forKey:key];
39+
}
40+
41+
return dict;
42+
}
43+
44+
@end

0 commit comments

Comments
 (0)