Skip to content

Commit 3848f48

Browse files
RSNarafacebook-github-bot
authored andcommitted
Use JavaScript functions for Component templates
Summary: ## Rationale **Disclaimer**: This is an incremental step towards more maintainable/readable react-native-codegen generators. In the future, we may want to replace these templates/string concat logic with *something better*. But until we decide what that *something better* is, let's at least get rid of all this gross string find/replace. Benefits of using Function templates over String.prototype.replace. - **Self-documenting**: Template Functions enumerate/describe their exact data dependencies in their signature. You no longer have to read the template implementation to see what data you need to pass into the template. - **Improved Readability**: JavaScript syntax highlighting makes it really easy to see where/how the data is inserted into the templates. Also template variables used be prefixed/suffixed with ::, which made things really confusing in C++ code (e.g: wtf is `::_CLASSNAME_::EventEmitter::::_EVENT_NAME_::`?). - **Simpler Interpolation**: Don't have to worry about .replaceAll vs .replace, or calling these replace functions with regexes or strings. - **Template Type-safety**: Ensure that the correct data types are passed to the component templates (e.g: flow will complain if you accidentally pass null/undefined when a template expects a string). - **Template Type-safety**: Ensure that we don't pass in extra data to templates (this diff catches/fixes instances of this error). Ensure that we don't forget to pass in data to the template. - etc. After this diff, both our Component and NativeModule generators will be using template functions. This string find/replace exists no more in react-native-codegen. This is also a very surface-level change. I made no efforts to simplify these templates. Let's take a look at that later, as necessary. Changelog: [Internal] Reviewed By: yungsters Differential Revision: D32021441 fbshipit-source-id: f8f27069bcbf9d66dcafb7d1411da1f938eb6dcd
1 parent b0711f1 commit 3848f48

12 files changed

+636
-363
lines changed

packages/react-native-codegen/src/generators/components/GenerateComponentDescriptorH.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ import type {SchemaType} from '../../CodegenSchema';
1515
// File path -> contents
1616
type FilesOutput = Map<string, string>;
1717

18-
const template = `
18+
const FileTemplate = ({
19+
componentDescriptors,
20+
libraryName,
21+
}: {
22+
componentDescriptors: string,
23+
libraryName: string,
24+
}) => `
1925
/**
2026
* ${'C'}opyright (c) Facebook, Inc. and its affiliates.
2127
*
@@ -27,20 +33,21 @@ const template = `
2733
2834
#pragma once
2935
30-
#include <react/renderer/components/::_LIBRARY_::/ShadowNodes.h>
36+
#include <react/renderer/components/${libraryName}/ShadowNodes.h>
3137
#include <react/renderer/core/ConcreteComponentDescriptor.h>
3238
3339
namespace facebook {
3440
namespace react {
3541
36-
::_COMPONENT_DESCRIPTORS_::
42+
${componentDescriptors}
3743
3844
} // namespace react
3945
} // namespace facebook
4046
`;
4147

42-
const componentTemplate = `
43-
using ::_CLASSNAME_::ComponentDescriptor = ConcreteComponentDescriptor<::_CLASSNAME_::ShadowNode>;
48+
const ComponentTemplate = ({className}: {className: string}) =>
49+
`
50+
using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>;
4451
`.trim();
4552

4653
module.exports = {
@@ -70,16 +77,18 @@ module.exports = {
7077
if (components[componentName].interfaceOnly === true) {
7178
return;
7279
}
73-
return componentTemplate.replace(/::_CLASSNAME_::/g, componentName);
80+
81+
return ComponentTemplate({className: componentName});
7482
})
7583
.join('\n');
7684
})
7785
.filter(Boolean)
7886
.join('\n');
7987

80-
const replacedTemplate = template
81-
.replace(/::_COMPONENT_DESCRIPTORS_::/g, componentDescriptors)
82-
.replace('::_LIBRARY_::', libraryName);
88+
const replacedTemplate = FileTemplate({
89+
componentDescriptors,
90+
libraryName,
91+
});
8392

8493
return new Map([[fileName, replacedTemplate]]);
8594
},

packages/react-native-codegen/src/generators/components/GenerateComponentHObjCpp.js

Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -37,53 +37,96 @@ function getOrdinalNumber(num: number): string {
3737
return 'unknown';
3838
}
3939

40-
const protocolTemplate = `
41-
@protocol RCT::_COMPONENT_NAME_::ViewProtocol <NSObject>
42-
::_METHODS_::
40+
const ProtocolTemplate = ({
41+
componentName,
42+
methods,
43+
}: {
44+
componentName: string,
45+
methods: string,
46+
}) =>
47+
`
48+
@protocol RCT${componentName}ViewProtocol <NSObject>
49+
${methods}
4350
@end
4451
`.trim();
4552

46-
const commandHandlerIfCaseConvertArgTemplate = `
47-
NSObject *arg::_ARG_NUMBER_:: = args[::_ARG_NUMBER_::];
53+
const CommandHandlerIfCaseConvertArgTemplate = ({
54+
componentName,
55+
expectedKind,
56+
argNumber,
57+
argNumberString,
58+
expectedKindString,
59+
argConversion,
60+
}: {
61+
componentName: string,
62+
expectedKind: string,
63+
argNumber: number,
64+
argNumberString: string,
65+
expectedKindString: string,
66+
argConversion: string,
67+
}) =>
68+
`
69+
NSObject *arg${argNumber} = args[${argNumber}];
4870
#if RCT_DEBUG
49-
if (!RCTValidateTypeOfViewCommandArgument(arg::_ARG_NUMBER_::, ::_EXPECTED_KIND_::, @"::_EXPECTED_KIND_STRING_::", @"::_COMPONENT_NAME_::", commandName, @"::_ARG_NUMBER_STR_::")) {
71+
if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, @"${expectedKindString}", @"${componentName}", commandName, @"${argNumberString}")) {
5072
return;
5173
}
5274
#endif
53-
::_ARG_CONVERSION_::
75+
${argConversion}
5476
`.trim();
5577

56-
const commandHandlerIfCaseTemplate = `
57-
if ([commandName isEqualToString:@"::_COMMAND_NAME_::"]) {
78+
const CommandHandlerIfCaseTemplate = ({
79+
componentName,
80+
commandName,
81+
numArgs,
82+
convertArgs,
83+
commandCall,
84+
}: {
85+
componentName: string,
86+
commandName: string,
87+
numArgs: number,
88+
convertArgs: string,
89+
commandCall: string,
90+
}) =>
91+
`
92+
if ([commandName isEqualToString:@"${commandName}"]) {
5893
#if RCT_DEBUG
59-
if ([args count] != ::_NUM_ARGS_::) {
60-
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"::_COMPONENT_NAME_::", commandName, (int)[args count], ::_NUM_ARGS_::);
94+
if ([args count] != ${numArgs}) {
95+
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"${componentName}", commandName, (int)[args count], ${numArgs});
6196
return;
6297
}
6398
#endif
6499
65-
::_CONVERT_ARGS_::
100+
${convertArgs}
66101
67-
::_COMMAND_CALL_::
102+
${commandCall}
68103
return;
69104
}
70105
`.trim();
71106

72-
const commandHandlerTemplate = `
73-
RCT_EXTERN inline void RCT::_COMPONENT_NAME_::HandleCommand(
74-
id<RCT::_COMPONENT_NAME_::ViewProtocol> componentView,
107+
const CommandHandlerTemplate = ({
108+
componentName,
109+
ifCases,
110+
}: {
111+
componentName: string,
112+
ifCases: string,
113+
}) =>
114+
`
115+
RCT_EXTERN inline void RCT${componentName}HandleCommand(
116+
id<RCT${componentName}ViewProtocol> componentView,
75117
NSString const *commandName,
76118
NSArray const *args)
77119
{
78-
::_IF_CASES_::
120+
${ifCases}
79121
80122
#if RCT_DEBUG
81-
RCTLogError(@"%@ received command %@, which is not a supported command.", @"::_COMPONENT_NAME_::", commandName);
123+
RCTLogError(@"%@ received command %@, which is not a supported command.", @"${componentName}", commandName);
82124
#endif
83125
}
84126
`.trim();
85127

86-
const template = `
128+
const FileTemplate = ({componentContent}: {componentContent: string}) =>
129+
`
87130
/**
88131
* ${'C'}opyright (c) Facebook, Inc. and its affiliates.
89132
*
@@ -99,7 +142,7 @@ const template = `
99142
100143
NS_ASSUME_NONNULL_BEGIN
101144
102-
::_COMPONENT_CONTENT_::
145+
${componentContent}
103146
104147
NS_ASSUME_NONNULL_END
105148
`.trim();
@@ -225,7 +268,7 @@ function generateProtocol(
225268
component: ComponentShape,
226269
componentName: string,
227270
): string {
228-
const commands = component.commands
271+
const methods = component.commands
229272
.map(command => {
230273
const params = command.typeAnnotation.params;
231274
const paramString =
@@ -245,9 +288,10 @@ function generateProtocol(
245288
.join('\n')
246289
.trim();
247290

248-
return protocolTemplate
249-
.replace(/::_COMPONENT_NAME_::/g, componentName)
250-
.replace('::_METHODS_::', commands);
291+
return ProtocolTemplate({
292+
componentName,
293+
methods,
294+
});
251295
}
252296

253297
function generateConvertAndValidateParam(
@@ -262,13 +306,14 @@ function generateConvertAndValidateParam(
262306
param.name
263307
} = ${getObjCRightHandAssignmentParamType(param, index)};`;
264308

265-
return commandHandlerIfCaseConvertArgTemplate
266-
.replace(/::_COMPONENT_NAME_::/g, componentName)
267-
.replace('::_ARG_CONVERSION_::', argConversion)
268-
.replace(/::_ARG_NUMBER_::/g, '' + index)
269-
.replace('::_ARG_NUMBER_STR_::', getOrdinalNumber(index + 1))
270-
.replace('::_EXPECTED_KIND_::', expectedKind)
271-
.replace('::_EXPECTED_KIND_STRING_::', expectedKindString);
309+
return CommandHandlerIfCaseConvertArgTemplate({
310+
componentName,
311+
argConversion,
312+
argNumber: index,
313+
argNumberString: getOrdinalNumber(index + 1),
314+
expectedKind,
315+
expectedKindString,
316+
});
272317
}
273318

274319
function generateCommandIfCase(
@@ -294,12 +339,13 @@ function generateCommandIfCase(
294339
.join(' ');
295340
const commandCall = `[componentView ${command.name}${commandCallArgs}];`;
296341

297-
return commandHandlerIfCaseTemplate
298-
.replace(/::_COMPONENT_NAME_::/g, componentName)
299-
.replace(/::_COMMAND_NAME_::/g, command.name)
300-
.replace(/::_NUM_ARGS_::/g, '' + params.length)
301-
.replace('::_CONVERT_ARGS_::', convertArgs)
302-
.replace('::_COMMAND_CALL_::', commandCall);
342+
return CommandHandlerIfCaseTemplate({
343+
componentName,
344+
commandName: command.name,
345+
numArgs: params.length,
346+
convertArgs,
347+
commandCall,
348+
});
303349
}
304350

305351
function generateCommandHandler(
@@ -314,9 +360,10 @@ function generateCommandHandler(
314360
.map(command => generateCommandIfCase(command, componentName))
315361
.join('\n\n');
316362

317-
return commandHandlerTemplate
318-
.replace(/::_COMPONENT_NAME_::/g, componentName)
319-
.replace('::_IF_CASES_::', ifCases);
363+
return CommandHandlerTemplate({
364+
componentName,
365+
ifCases,
366+
});
320367
}
321368

322369
module.exports = {
@@ -362,10 +409,9 @@ module.exports = {
362409
.filter(Boolean)
363410
.join('\n\n');
364411

365-
const replacedTemplate = template.replace(
366-
'::_COMPONENT_CONTENT_::',
412+
const replacedTemplate = FileTemplate({
367413
componentContent,
368-
);
414+
});
369415

370416
return new Map([[fileName, replacedTemplate]]);
371417
},

0 commit comments

Comments
 (0)