UNPKG

@react-native-windows/codegen

Version:

Generators for react-native-codegen targeting react-native-windows

564 lines (483 loc) 22.4 kB
/** * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. * @format */ 'use strict'; import type { SchemaType, EventTypeAnnotation, PropTypeAnnotation, ObjectTypeAnnotation, CommandParamTypeAnnotation, } from '@react-native/codegen/lib/CodegenSchema'; import {getAliasCppName, setPreferredModuleName} from './AliasManaging'; import { translateComponentPropsFieldType, translateComponentEventType, translateCommandParamType, } from './PropObjectTypes'; import type {CppStringTypes} from './ObjectTypes'; import type {AliasMap} from './AliasManaging'; export type {CppStringTypes} from './ObjectTypes'; type FilesOutput = Map<string, string>; const headerTemplate = `/* * This file is auto-generated from ::_COMPONENT_NAME_::NativeComponent spec file in flow / TypeScript. */ // clang-format off #pragma once #include <NativeModules.h> #ifdef RNW_NEW_ARCH #include <JSValueComposition.h> #include <winrt/Microsoft.ReactNative.Composition.h> #include <winrt/Microsoft.UI.Composition.h> #endif // #ifdef RNW_NEW_ARCH`; const propsTemplate = `REACT_STRUCT(::_PROPS_NAME_::) struct ::_PROPS_NAME_:: : winrt::implements<::_PROPS_NAME_::, winrt::Microsoft::ReactNative::IComponentProps> { ::_PROPS_NAME_::(winrt::Microsoft::ReactNative::ViewProps props, const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) : ViewProps(props) { if (cloneFrom) { auto cloneFromProps = cloneFrom.as<::_PROPS_NAME_::>(); ::_PROP_INITIALIZERS_:: } } void SetProp(uint32_t hash, winrt::hstring propName, winrt::Microsoft::ReactNative::IJSValueReader value) noexcept { winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this); } ::_PROPS_FIELDS_:: const winrt::Microsoft::ReactNative::ViewProps ViewProps; };`; const propsObjectTemplate = `REACT_STRUCT(::_OBJECT_NAME_::) struct ::_OBJECT_NAME_:: { ::_OBJECT_FIELDS_::}; `; const eventsObjectTemplate = `REACT_STRUCT(::_OBJECT_NAME_::) struct ::_OBJECT_NAME_:: { ::_OBJECT_FIELDS_::}; `; const eventEmitterMethodTemplate = ` void ::_EVENT_NAME_::(::_EVENT_OBJECT_TYPE_:: &value) const { m_eventEmitter.DispatchEvent(L"::_EVENT_NAME_NO_ON_::", [value](const winrt::Microsoft::ReactNative::IJSValueWriter writer) { winrt::Microsoft::ReactNative::WriteValue(writer, value); }); }`; const eventEmitterTemplate = `::_COMPONENT_EVENT_OBJECT_TYPES_:: struct ::_EVENT_EMITTER_NAME_:: { ::_EVENT_EMITTER_NAME_::(const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) : m_eventEmitter(eventEmitter) {} ::_EVENT_EMITTER_USINGS_:: ::_EVENT_EMITTER_METHODS_:: private: winrt::Microsoft::ReactNative::EventEmitter m_eventEmitter{nullptr}; };`; const baseStructTemplate = ` template<typename TUserData> struct Base::_COMPONENT_NAME_:: { virtual void UpdateProps( const winrt::Microsoft::ReactNative::ComponentView &/*view*/, const winrt::com_ptr<::_COMPONENT_NAME_::Props> &newProps, const winrt::com_ptr<::_COMPONENT_NAME_::Props> &/*oldProps*/) noexcept { m_props = newProps; } // UpdateLayoutMetrics will only be called if this method is overridden virtual void UpdateLayoutMetrics( const winrt::Microsoft::ReactNative::ComponentView &/*view*/, const winrt::Microsoft::ReactNative::LayoutMetrics &/*newLayoutMetrics*/, const winrt::Microsoft::ReactNative::LayoutMetrics &/*oldLayoutMetrics*/) noexcept { } // UpdateState will only be called if this method is overridden virtual void UpdateState( const winrt::Microsoft::ReactNative::ComponentView &/*view*/, const winrt::Microsoft::ReactNative::IComponentState &/*newState*/) noexcept { } virtual void UpdateEventEmitter(const std::shared_ptr<::_COMPONENT_NAME_::EventEmitter> &eventEmitter) noexcept { m_eventEmitter = eventEmitter; } // MountChildComponentView will only be called if this method is overridden virtual void MountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &/*args*/) noexcept { } // UnmountChildComponentView will only be called if this method is overridden virtual void UnmountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &/*args*/) noexcept { } // Initialize will only be called if this method is overridden virtual void Initialize(const winrt::Microsoft::ReactNative::ComponentView &/*view*/) noexcept { } // CreateVisual will only be called if this method is overridden virtual winrt::Microsoft::UI::Composition::Visual CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { return view.as<winrt::Microsoft::ReactNative::Composition::ComponentView>().Compositor().CreateSpriteVisual(); } // FinalizeUpdate will only be called if this method is overridden virtual void FinalizeUpdate(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, winrt::Microsoft::ReactNative::ComponentViewUpdateMask /*mask*/) noexcept { } ::_COMPONENT_VIEW_COMMAND_HANDLERS_:: ::_COMPONENT_VIEW_COMMAND_HANDLER_:: const std::shared_ptr<::_COMPONENT_NAME_::EventEmitter>& EventEmitter() const { return m_eventEmitter; } const winrt::com_ptr<::_COMPONENT_NAME_::Props>& Props() const { return m_props; } private: winrt::com_ptr<::_COMPONENT_NAME_::Props> m_props; std::shared_ptr<::_COMPONENT_NAME_::EventEmitter> m_eventEmitter; }; `; const registerTemplate = ` template <typename TUserData> void Register::_COMPONENT_NAME_::NativeComponent( winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder, std::function<void(const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder&)> builderCallback) noexcept { packageBuilder.as<winrt::Microsoft::ReactNative::IReactPackageBuilderFabric>().AddViewComponent( L"::_COMPONENT_NAME_::", [builderCallback](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept { auto compBuilder = builder.as<winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder>(); builder.SetCreateProps([](winrt::Microsoft::ReactNative::ViewProps props, const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) noexcept { return winrt::make<::_COMPONENT_NAME_::Props>(props, cloneFrom); }); builder.SetUpdatePropsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::IComponentProps &newProps, const winrt::Microsoft::ReactNative::IComponentProps &oldProps) noexcept { auto userData = view.UserData().as<TUserData>(); userData->UpdateProps(view, newProps ? newProps.as<::_COMPONENT_NAME_::Props>() : nullptr, oldProps ? oldProps.as<::_COMPONENT_NAME_::Props>() : nullptr); }); compBuilder.SetUpdateLayoutMetricsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::LayoutMetrics &newLayoutMetrics, const winrt::Microsoft::ReactNative::LayoutMetrics &oldLayoutMetrics) noexcept { auto userData = view.UserData().as<TUserData>(); userData->UpdateLayoutMetrics(view, newLayoutMetrics, oldLayoutMetrics); }); builder.SetUpdateEventEmitterHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) noexcept { auto userData = view.UserData().as<TUserData>(); userData->UpdateEventEmitter(std::make_shared<::_COMPONENT_NAME_::EventEmitter>(eventEmitter)); }); if constexpr (&TUserData::FinalizeUpdate != &Base::_COMPONENT_NAME_::<TUserData>::FinalizeUpdate) { builder.SetFinalizeUpdateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, winrt::Microsoft::ReactNative::ComponentViewUpdateMask mask) noexcept { auto userData = view.UserData().as<TUserData>(); userData->FinalizeUpdate(view, mask); }); } if constexpr (&TUserData::UpdateState != &Base::_COMPONENT_NAME_::<TUserData>::UpdateState) { builder.SetUpdateStateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::IComponentState &newState) noexcept { auto userData = view.UserData().as<TUserData>(); userData->member(view, newState); }); } ::_REGISTER_CUSTOM_COMMAND_HANDLER_:: if constexpr (&TUserData::MountChildComponentView != &Base::_COMPONENT_NAME_::<TUserData>::MountChildComponentView) { builder.SetMountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &args) noexcept { auto userData = view.UserData().as<TUserData>(); return userData->MountChildComponentView(view, args); }); } if constexpr (&TUserData::UnmountChildComponentView != &Base::_COMPONENT_NAME_::<TUserData>::UnmountChildComponentView) { builder.SetUnmountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &args) noexcept { auto userData = view.UserData().as<TUserData>(); return userData->UnmountChildComponentView(view, args); }); } compBuilder.SetViewComponentViewInitializer([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { auto userData = winrt::make_self<TUserData>(); if constexpr (&TUserData::Initialize != &Base::_COMPONENT_NAME_::<TUserData>::Initialize) { userData->Initialize(view); } view.UserData(*userData); }); if constexpr (&TUserData::CreateVisual != &Base::_COMPONENT_NAME_::<TUserData>::CreateVisual) { compBuilder.SetCreateVisualHandler([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { auto userData = view.UserData().as<TUserData>(); return userData->CreateVisual(view); }); } // Allow app to further customize the builder if (builderCallback) { builderCallback(compBuilder); } }); } `; const fileTemplate = ` ${headerTemplate} #ifdef RNW_NEW_ARCH namespace ::_NAMESPACE_:: { ::_COMPONENT_PROP_OBJECT_TYPES_:: ::_COMPONENT_PROP_TYPES_:: ::_COMPONENT_EVENT_EMITTER_:: ::_BASE_COMPONENT_STRUCT_:: ::_COMPONENT_REGISTRATION_:: } // namespace ::_NAMESPACE_:: #endif // #ifdef RNW_NEW_ARCH `; function capitalizeFirstLetter(s: string) { return s.charAt(0).toUpperCase() + s.slice(1); } export function createComponentGenerator({ namespace, cppStringType, }: { namespace: string; cppStringType: CppStringTypes; }) { return ( _libraryName: string, schema: SchemaType, _moduleSpecName: string, ): FilesOutput => { const files = new Map<string, string>(); const cppCodegenOptions = {cppStringType}; for (const componentName of Object.keys(schema.modules)) { const component = schema.modules[componentName]; setPreferredModuleName(componentName); if (component.type === 'Component') { console.log(`Generating ${componentName}.g.h`); const componentShape = component.components[componentName]; componentShape.extendsProps.forEach(propsBaseType => { if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition propsBaseType.type !== 'ReactNativeBuiltInType' || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition propsBaseType.knownTypeName !== 'ReactNativeCoreViewProps' ) { throw new Error( 'Currently only supports props extending from ViewProps', ); } }); // Props const propObjectAliases: AliasMap< ObjectTypeAnnotation<PropTypeAnnotation> > = {types: {}, jobs: []}; const propsName = `${componentName}Props`; const propsFields = componentShape.props .map(prop => { const propType = translateComponentPropsFieldType( prop.typeAnnotation, propObjectAliases, `${propsName}_${prop.name}`, cppCodegenOptions, ); return ` REACT_FIELD(${prop.name})\n ${ prop.optional && !propType.alreadySupportsOptionalOrHasDefault ? `std::optional<${propType.type}>` : propType.type } ${prop.name}${propType.initializer};\n`; }) .join('\n'); const propInitializers = componentShape.props .map(prop => { return ` ${prop.name} = cloneFromProps->${prop.name};`; }) .join('\n'); const propObjectTypes = propObjectAliases.jobs .map(propObjectTypeName => { const propObjectType = propObjectAliases.types[propObjectTypeName]!; const propsObjectFields = propObjectType.properties .map(property => { const propType = translateComponentPropsFieldType( property.typeAnnotation, propObjectAliases, `${propsName}_${property.name}`, cppCodegenOptions, ); return ` REACT_FIELD(${property.name})\n ${ property.optional && !propType.alreadySupportsOptionalOrHasDefault ? `std::optional<${propType.type}>` : propType.type } ${property.name}${propType.initializer};\n`; }) .join('\n'); return propsObjectTemplate .replace( /::_OBJECT_NAME_::/g, getAliasCppName(propObjectTypeName), ) .replace(/::_OBJECT_FIELDS_::/g, propsObjectFields); }) .join('\n'); // Events const eventObjectAliases: AliasMap< ObjectTypeAnnotation<EventTypeAnnotation> > = {types: {}, jobs: []}; const eventEmitterName = `${componentName}EventEmitter`; const eventEmitterMethods = componentShape.events .filter(event => event.typeAnnotation.argument) .map(event => { if (event.typeAnnotation.argument?.baseTypes) { throw new Error( 'Events with base type arguments not currently supported', ); } // Called to collect the eventObjectAliases translateComponentEventType( event.typeAnnotation.argument!, eventObjectAliases, `${event.name}`, cppCodegenOptions, ); // onSomething -> something let eventNameLower = event.name.replace('on', ''); eventNameLower = eventNameLower[0].toLowerCase() + eventNameLower.slice(1); return eventEmitterMethodTemplate .replace(/::_EVENT_NAME_::/g, event.name) .replace(/::_EVENT_NAME_NO_ON_::/g, eventNameLower) .replace( /::_EVENT_OBJECT_TYPE_::/g, event.name.replace('on', 'On'), ); }) .join('\n\n'); const eventObjects = eventObjectAliases.jobs .map(eventObjectTypeName => { const eventObjectType = eventObjectAliases.types[eventObjectTypeName]!; const eventObjectFields = eventObjectType.properties .map(property => { const eventPropType = translateComponentEventType( property.typeAnnotation, eventObjectAliases, eventObjectTypeName, cppCodegenOptions, ); return ` REACT_FIELD(${property.name})\n ${ property.optional && !eventPropType.alreadySupportsOptionalOrHasDefault ? `std::optional<${eventPropType.type}>` : eventPropType.type } ${property.name}${eventPropType.initializer};\n`; }) .join('\n'); return eventsObjectTemplate .replace( /::_OBJECT_NAME_::/g, `${componentName}_${eventObjectTypeName.replace('on', 'On')}`, ) .replace(/::_OBJECT_FIELDS_::/g, eventObjectFields); }) .join('\n'); const eventObjectUsings = eventObjectAliases.jobs .map(eventObjectTypeName => { return ` using ${eventObjectTypeName.replace( 'on', 'On', )} = ${componentName}_${eventObjectTypeName.replace('on', 'On')};`; }) .join('\n'); const eventEmitter = eventEmitterTemplate .replace(/::_COMPONENT_EVENT_OBJECT_TYPES_::/g, eventObjects) .replace(/::_EVENT_EMITTER_METHODS_::/g, eventEmitterMethods) .replace(/::_EVENT_EMITTER_USINGS_::/g, eventObjectUsings); // Commands const commandAliases: AliasMap< ObjectTypeAnnotation<CommandParamTypeAnnotation> > = {types: {}, jobs: []}; const hasAnyCommands = componentShape.commands.length !== 0; const commandHandlers = hasAnyCommands ? componentShape.commands .map(command => { const commandArgs = command.typeAnnotation.params .map(param => { const commandArgType = translateCommandParamType( param.typeAnnotation, commandAliases, `${componentName}_${command.name}`, cppCodegenOptions, ); return `${ param.optional && !commandArgType.alreadySupportsOptionalOrHasDefault ? `std::optional<${commandArgType.type}>` : commandArgType.type } ${param.name}`; }) .join(', '); return ` // You must provide an implementation of this method to handle the "${ command.name }" command virtual void Handle${capitalizeFirstLetter( command.name, )}Command(${commandArgs}) noexcept = 0;`; }) .join('\n\n') : ''; const commandHandler = hasAnyCommands ? `void HandleCommand(const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::HandleCommandArgs& args) noexcept { auto userData = view.UserData().as<TUserData>(); auto commandName = args.CommandName(); ${componentShape.commands .map(command => { const commaSeparatedCommandArgs = command.typeAnnotation.params .map(param => param.name) .join(', '); return ` if (commandName == L"${command.name}") { ${ command.typeAnnotation.params.length !== 0 ? ` ${command.typeAnnotation.params .map(param => { const commandArgType = translateCommandParamType( param.typeAnnotation, commandAliases, `${componentName}_${command.name}`, cppCodegenOptions, ); return `${ param.optional && !commandArgType.alreadySupportsOptionalOrHasDefault ? `std::optional<${commandArgType.type}>` : commandArgType.type } ${param.name};`; }) .join('\n')} winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), ${commaSeparatedCommandArgs});` : '' } userData->Handle${capitalizeFirstLetter( command.name, )}Command(${commaSeparatedCommandArgs}); return; }`; }) .join('\n\n')} }` : ''; const registerCommandHandler = hasAnyCommands ? ` builder.SetCustomCommandHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::HandleCommandArgs& args) noexcept { auto userData = view.UserData().as<TUserData>(); userData->HandleCommand(view, args); });` : ''; const baseType = baseStructTemplate .replace(/::_COMPONENT_VIEW_COMMAND_HANDLERS_::/g, commandHandlers) .replace(/::_COMPONENT_VIEW_COMMAND_HANDLER_::/g, commandHandler); // Registration const componentRegistration = registerTemplate.replace( /::_REGISTER_CUSTOM_COMMAND_HANDLER_::/g, registerCommandHandler, ); // Final output const replaceContent = function (template: string): string { return template .replace(/::_COMPONENT_PROP_OBJECT_TYPES_::/g, propObjectTypes) .replace(/::_COMPONENT_PROP_TYPES_::/g, propsTemplate) .replace(/::_COMPONENT_EVENT_EMITTER_::/g, eventEmitter) .replace(/::_BASE_COMPONENT_STRUCT_::/g, baseType) .replace(/::_COMPONENT_REGISTRATION_::/g, componentRegistration) .replace(/::_EVENT_EMITTER_NAME_::/g, eventEmitterName) .replace(/::_PROPS_NAME_::/g, propsName) .replace(/::_COMPONENT_NAME_::/g, componentName) .replace(/::_PROP_INITIALIZERS_::/g, propInitializers) .replace(/::_PROPS_FIELDS_::/g, propsFields) .replace(/::_NAMESPACE_::/g, namespace) .replace(/\n\n\n+/g, '\n\n'); }; files.set(`${componentName}.g.h`, replaceContent(fileTemplate)); } } return files; }; }