Skip to content

Commit

Permalink
Support emitting typed RCTDeviceEmitter events
Browse files Browse the repository at this point in the history
Summary:
This enables to code-gen base C++ types for custom exported JS types from a RN TM spec - which have been previously excluded from code-gen as these aren't used in any function.

The only work around so far was to ‘register’ a random function using the custom type which should be used for RCTDeviceEventEmitter events

Changelog: [Internal]

Differential Revision: D56685903
  • Loading branch information
christophpurrer authored and facebook-github-bot committed Apr 30, 2024
1 parent b7de916 commit 4797dbe
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 12 deletions.
Expand Up @@ -742,6 +742,12 @@ export type GraphNode = {
neighbors?: Array<GraphNode>,
};
export type CustomDeviceEvent = {
type: string,
level: number,
degree?: number,
};
export interface Spec extends TurboModule {
+getCallback: () => () => void;
+getMixed: (arg: mixed) => mixed;
Expand Down
Expand Up @@ -102,6 +102,32 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`]
}
}
]
},
'CustomDeviceEvent': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'type',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation'
}
},
{
'name': 'level',
'optional': false,
'typeAnnotation': {
'type': 'NumberTypeAnnotation'
}
},
{
'name': 'degree',
'optional': true,
'typeAnnotation': {
'type': 'NumberTypeAnnotation'
}
}
]
}
},
'enumMap': {
Expand Down
63 changes: 57 additions & 6 deletions packages/react-native-codegen/src/parsers/parsers-commons.js
Expand Up @@ -171,6 +171,52 @@ function isObjectProperty(property: $FlowFixMe, language: ParserType): boolean {
}
}

function getObjectTypeAnnotations(
hasteModuleName: string,
types: TypeDeclarationMap,
tryParse: ParserErrorCapturer,
translateTypeAnnotation: $FlowFixMe,
parser: Parser,
): {...NativeModuleAliasMap} {
const aliasMap: {...NativeModuleAliasMap} = {};
Object.entries(types).forEach(([key, value]) => {
const isTypeAlias =
value.type === 'TypeAlias' || value.type === 'TSTypeAliasDeclaration';
if (!isTypeAlias) {
return;
}
const parent = parser.nextNodeForTypeAlias(value);
if (
parent.type !== 'ObjectTypeAnnotation' &&
parent.type !== 'TSTypeLiteral'
) {
return;
}
const typeProperties = parser
.getAnnotatedElementProperties(value)
.map(prop =>
parseObjectProperty(
parent,
prop,
hasteModuleName,
types,
aliasMap,
{}, // enumMap
tryParse,
true, // cxxOnly
prop?.optional || false,
translateTypeAnnotation,
parser,
),
);
aliasMap[key] = {
type: 'ObjectTypeAnnotation',
properties: typeProperties,
};
});
return aliasMap;
}

function parseObjectProperty(
parentObject?: $FlowFixMe,
property: $FlowFixMe,
Expand Down Expand Up @@ -659,6 +705,16 @@ const buildModuleSchema = (
moduleName,
);

const aliasMap: {...NativeModuleAliasMap} = cxxOnly
? getObjectTypeAnnotations(
hasteModuleName,
types,
tryParse,
translateTypeAnnotation,
parser,
)
: {};

const properties: $ReadOnlyArray<$FlowFixMe> =
language === 'Flow' ? moduleSpec.body.properties : moduleSpec.body.body;

Expand All @@ -675,9 +731,7 @@ const buildModuleSchema = (
enumMap: NativeModuleEnumMap,
propertyShape: NativeModulePropertyShape,
}>(property => {
const aliasMap: {...NativeModuleAliasMap} = {};
const enumMap: {...NativeModuleEnumMap} = {};

return tryParse(() => ({
aliasMap,
enumMap,
Expand All @@ -696,10 +750,7 @@ const buildModuleSchema = (
})
.filter(Boolean)
.reduce(
(
moduleSchema: NativeModuleSchema,
{aliasMap, enumMap, propertyShape},
) => ({
(moduleSchema: NativeModuleSchema, {enumMap, propertyShape}) => ({
type: 'NativeModule',
aliasMap: {...moduleSchema.aliasMap, ...aliasMap},
enumMap: {...moduleSchema.enumMap, ...enumMap},
Expand Down
Expand Up @@ -836,6 +836,12 @@ export type GraphNode = {
neighbors?: Array<GraphNode>,
};
export type CustomDeviceEvent = {
type: string,
level: number,
degree?: number,
};
export interface Spec extends TurboModule {
readonly getCallback: () => () => void;
readonly getMixed: (arg: unknown) => unknown;
Expand Down
Expand Up @@ -93,6 +93,32 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL
}
}
]
},
'CustomDeviceEvent': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'type',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation'
}
},
{
'name': 'level',
'optional': false,
'typeAnnotation': {
'type': 'NumberTypeAnnotation'
}
},
{
'name': 'degree',
'optional': true,
'typeAnnotation': {
'type': 'NumberTypeAnnotation'
}
}
]
}
},
'enumMap': {
Expand Down
Expand Up @@ -180,15 +180,18 @@ void NativeCxxModuleExample::setMenu(jsi::Runtime& rt, MenuItem menuItem) {

void NativeCxxModuleExample::emitCustomDeviceEvent(
jsi::Runtime& rt,
jsi::String eventName) {
const std::string& eventName) {
// Test emitting device events (RCTDeviceEventEmitter.emit) from C++
// TurboModule with arbitrary arguments
// TurboModule with arbitrary arguments. This can be called from any thread
emitDeviceEvent(
eventName.utf8(rt).c_str(),
[](jsi::Runtime& rt, std::vector<jsi::Value>& args) {
eventName,
[jsInvoker = jsInvoker_](
jsi::Runtime& rt, std::vector<jsi::Value>& args) {
args.emplace_back(jsi::Value(true));
args.emplace_back(jsi::Value(42));
args.emplace_back(jsi::String::createFromAscii(rt, "stringArg"));
args.emplace_back(bridging::toJs(
rt, CustomDeviceEvent{"one", 2, std::nullopt}, jsInvoker));
});
}

Expand Down
Expand Up @@ -116,6 +116,17 @@ template <>
struct Bridging<MenuItem>
: NativeCxxModuleExampleCxxMenuItemBridging<MenuItem> {};

#pragma mark - RCTDeviceEventEmitter events

using CustomDeviceEvent = NativeCxxModuleExampleCxxCustomDeviceEvent<
std::string,
int32_t,
std::optional<float>>;

template <>
struct Bridging<CustomDeviceEvent>
: NativeCxxModuleExampleCxxCustomDeviceEventBridging<CustomDeviceEvent> {};

#pragma mark - implementation
class NativeCxxModuleExample
: public NativeCxxModuleExampleCxxSpec<NativeCxxModuleExample> {
Expand Down Expand Up @@ -185,7 +196,7 @@ class NativeCxxModuleExample

void setMenu(jsi::Runtime& rt, MenuItem menuItem);

void emitCustomDeviceEvent(jsi::Runtime& rt, jsi::String eventName);
void emitCustomDeviceEvent(jsi::Runtime& rt, const std::string& eventName);

void voidFuncThrows(jsi::Runtime& rt);

Expand Down
Expand Up @@ -74,6 +74,12 @@ export type MenuItem = {
items?: Array<MenuItem>,
};

export type CustomDeviceEvent = {
type: string,
level: number,
degree?: ?number,
};

export interface Spec extends TurboModule {
+getArray: (arg: Array<ObjectStruct | null>) => Array<ObjectStruct | null>;
+getBool: (arg: boolean) => boolean;
Expand Down
Expand Up @@ -165,7 +165,7 @@ class NativeCxxModuleExampleExample extends React.Component<{||}, State> {
DeviceEventEmitter.addListener(CUSTOM_EVENT_TYPE, (...args) => {
this._setResult(
'emitDeviceEvent',
`${CUSTOM_EVENT_TYPE}(${args.map(s => `${s}`).join(', ')})`,
`${CUSTOM_EVENT_TYPE}(${args.map(s => (typeof s === 'object' ? JSON.stringify(s) : s)).join(', ')})`,
);
});
NativeCxxModuleExample?.emitCustomDeviceEvent(CUSTOM_EVENT_TYPE);
Expand Down

0 comments on commit 4797dbe

Please sign in to comment.