From 2bd503285e82adfcbca87028f1dbb078f1f325de Mon Sep 17 00:00:00 2001 From: Eli White Date: Mon, 24 Jun 2019 18:51:26 -0700 Subject: [PATCH] Update View Config generator to create command methods Summary: Flow types like this: ``` interface NativeCommands { +hotspotUpdate: (viewRef: React.Ref<'RCTView'>, x: Int32, y: Int32) => void; } export const Commands = codegenNativeCommands(); ``` get turned into this: ``` export const Commands = { hotspotUpdate(viewRef: React.Ref<'RCTView'>, x: number, y: number) { UIManager.dispatchViewCommand( findNodeHandle(viewRef), UIManager.getViewManagerConfig('RCTView').Commands.hotspotUpdate, [x, y] ); } } ``` Reviewed By: rickhanlonii Differential Revision: D15953126 fbshipit-source-id: edbb91056347d021dd0683391c903b76f3d1c33f --- Libraries/Utilities/codegenNativeCommands.js | 17 ++++ .../__test_fixtures__/fixtures.js | 11 +++ .../__snapshots__/index-test.js.snap | 32 ++++++- .../src/generators/GenerateViewConfigJs.js | 94 ++++++++++++++++++- .../generators/__test_fixtures__/fixtures.js | 64 +++++++++++++ .../GenerateEventEmitterCpp-test.js.snap | 23 +++++ .../GenerateEventEmitterH-test.js.snap | 24 +++++ .../GeneratePropsCpp-test.js.snap | 29 ++++++ .../__snapshots__/GeneratePropsH-test.js.snap | 32 +++++++ .../GenerateShadowNodeCpp-test.js.snap | 23 +++++ .../GenerateShadowNodeH-test.js.snap | 33 +++++++ .../__snapshots__/GenerateTests-test.js.snap | 28 ++++++ .../GenerateViewConfigJs-test.js.snap | 52 ++++++++++ 13 files changed, 454 insertions(+), 8 deletions(-) create mode 100644 Libraries/Utilities/codegenNativeCommands.js diff --git a/Libraries/Utilities/codegenNativeCommands.js b/Libraries/Utilities/codegenNativeCommands.js new file mode 100644 index 00000000000..a6dc594f642 --- /dev/null +++ b/Libraries/Utilities/codegenNativeCommands.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +function codegenNativeCommands(): T { + return (({}: any): T); +} + +export default codegenNativeCommands; diff --git a/packages/babel-plugin-inline-view-configs/__test_fixtures__/fixtures.js b/packages/babel-plugin-inline-view-configs/__test_fixtures__/fixtures.js index b13aaa30a44..12c96cdfe4b 100644 --- a/packages/babel-plugin-inline-view-configs/__test_fixtures__/fixtures.js +++ b/packages/babel-plugin-inline-view-configs/__test_fixtures__/fixtures.js @@ -16,9 +16,13 @@ export default 'Not a view config' `; const FULL_NATIVE_COMPONENT = ` +// @flow + +const codegenNativeCommands = require('codegenNativeCommands'); const codegenNativeComponent = require('codegenNativeComponent'); import type { + Int32, BubblingEvent, DirectEvent, WithDefault, @@ -26,6 +30,11 @@ import type { import type {ViewProps} from 'ViewPropTypes'; +interface NativeCommands { + +hotspotUpdate: (viewRef: React.Ref<'RCTView'>, x: Int32, y: Int32) => void; + +scrollTo: (viewRef: React.Ref<'RCTView'>, y: Int32, animated: boolean) => void; +} + type ModuleProps = $ReadOnly<{| ...ViewProps, @@ -37,6 +46,8 @@ type ModuleProps = $ReadOnly<{| onBubblingEventDefinedInlineNull: (event: BubblingEvent) => void, |}>; +export const Commands = codegenNativeCommands(); + export default codegenNativeComponent('Module', { interfaceOnly: true, paperComponentName: 'RCTModule', diff --git a/packages/babel-plugin-inline-view-configs/__tests__/__snapshots__/index-test.js.snap b/packages/babel-plugin-inline-view-configs/__tests__/__snapshots__/index-test.js.snap index 4ea15951844..73b1021cc4f 100644 --- a/packages/babel-plugin-inline-view-configs/__tests__/__snapshots__/index-test.js.snap +++ b/packages/babel-plugin-inline-view-configs/__tests__/__snapshots__/index-test.js.snap @@ -1,10 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Babel plugin inline view configs can inline config for FullNativeComponent.js 1`] = ` -"const codegenNativeComponent = require('codegenNativeComponent'); +"// @flow +const codegenNativeCommands = require('codegenNativeCommands'); -import type { BubblingEvent, DirectEvent, WithDefault } from 'CodegenFlowtypes'; +const codegenNativeComponent = require('codegenNativeComponent'); + +import type { Int32, BubblingEvent, DirectEvent, WithDefault } from 'CodegenFlowtypes'; import type { ViewProps } from 'ViewPropTypes'; +interface NativeCommands { + +hotspotUpdate: (viewRef: React.Ref<'RCTView'>, x: Int32, y: Int32) => void, + +scrollTo: (viewRef: React.Ref<'RCTView'>, y: Int32, animated: boolean) => void, +} type ModuleProps = $ReadOnly<{| ...ViewProps, // Props boolean_default_true_optional_both?: ?WithDefault, @@ -12,9 +19,18 @@ type ModuleProps = $ReadOnly<{| ...ViewProps, onDirectEventDefinedInlineNull: (event: DirectEvent) => void, onBubblingEventDefinedInlineNull: (event: BubblingEvent) => void, |}>; +export const Commands = codegenNativeCommands(); const registerGeneratedViewConfig = require('registerGeneratedViewConfig'); +const { + UIManager +} = require(\\"react-native\\"); + +const { + findNodeHandle +} = require(\\"react-native\\"); + const ModuleViewConfig = { uiViewClassName: 'RCTModule', bubblingEventTypes: { @@ -39,7 +55,17 @@ const ModuleViewConfig = { let nativeComponentName = 'RCTModule'; registerGeneratedViewConfig(nativeComponentName, ModuleViewConfig); export const __INTERNAL_VIEW_CONFIG = ModuleViewConfig; -export default nativeComponentName;" +export default nativeComponentName; +export const Commands = { + hotspotUpdate(ref, x, y) { + UIManager.dispatchViewCommand(findNodeHandle(ref), UIManager.getViewManagerConfig(\\"RCTModule\\").Commands.hotspotUpdate, [x, y]); + }, + + scrollTo(ref, y, animated) { + UIManager.dispatchViewCommand(findNodeHandle(ref), UIManager.getViewManagerConfig(\\"RCTModule\\").Commands.scrollTo, [y, animated]); + } + +};" `; exports[`Babel plugin inline view configs can inline config for NotANativeComponent.js 1`] = ` diff --git a/packages/react-native-codegen/src/generators/GenerateViewConfigJs.js b/packages/react-native-codegen/src/generators/GenerateViewConfigJs.js index fa07f190ce8..1947dc45315 100644 --- a/packages/react-native-codegen/src/generators/GenerateViewConfigJs.js +++ b/packages/react-native-codegen/src/generators/GenerateViewConfigJs.js @@ -34,6 +34,10 @@ const template = ` ::_COMPONENT_CONFIG_:: `; +// We use this to add to a set. Need to make sure we aren't importing +// this multiple times. +const UIMANAGER_IMPORT = 'const {UIManager} = require("react-native")'; + function getReactDiffProcessValue(typeAnnotation) { switch (typeAnnotation.type) { case 'BooleanTypeAnnotation': @@ -239,6 +243,69 @@ function buildViewConfig( return j.objectExpression(properties); } +function buildCommands( + schema: SchemaType, + componentName: string, + component, + imports, +) { + const commands = component.commands; + + if (commands.length === 0) { + return null; + } + + imports.add(UIMANAGER_IMPORT); + imports.add('const {findNodeHandle} = require("react-native")'); + + const properties = commands.map(command => { + const commandName = command.name; + const params = command.typeAnnotation.params; + + const componentNameLiteral = j.literal(componentName); + const commandNameIdentifier = j.identifier(commandName); + const arrayParams = j.arrayExpression( + params.map(param => { + return j.identifier(param.name); + }), + ); + + const expression = j.template.expression` + UIManager.dispatchViewCommand( + findNodeHandle(ref), + UIManager.getViewManagerConfig(${componentNameLiteral}).Commands.${commandNameIdentifier}, + ${arrayParams} + ) + `; + + const functionParams = params.map(param => { + return j.identifier(param.name); + }); + + const property = j.property( + 'init', + commandNameIdentifier, + j.functionExpression( + null, + [j.identifier('ref'), ...functionParams], + j.blockStatement([j.expressionStatement(expression)]), + ), + ); + property.method = true; + + return property; + }); + + return j.exportNamedDeclaration( + j.variableDeclaration('const', [ + j.variableDeclarator( + j.identifier('Commands'), + j.objectExpression(properties), + ), + ]), + ); +} + module.exports = { generate(libraryName: string, schema: SchemaType): FilesOutput { try { @@ -262,7 +329,7 @@ module.exports = { : componentName; if (component.paperComponentNameDeprecated) { - imports.add('const {UIManager} = require("react-native")'); + imports.add(UIMANAGER_IMPORT); } const deprecatedCheckBlock = component.paperComponentNameDeprecated @@ -282,8 +349,9 @@ module.exports = { ) .replace(/::_DEPRECATION_CHECK_::/, deprecatedCheckBlock); - const replacedSource: string = j - .withParser('flow')(replacedTemplate) + const replacedSourceRoot = j.withParser('flow')(replacedTemplate); + + replacedSourceRoot .find(j.Identifier, { name: 'VIEW_CONFIG', }) @@ -294,8 +362,24 @@ module.exports = { component, imports, ), - ) - .toSource({quote: 'single', trailingComma: true}); + ); + + const commands = buildCommands( + schema, + paperComponentName, + component, + imports, + ); + if (commands) { + replacedSourceRoot + .find(j.ExportDefaultDeclaration) + .insertAfter(j(commands).toSource()); + } + + const replacedSource: string = replacedSourceRoot.toSource({ + quote: 'single', + trailingComma: true, + }); return replacedSource; }) diff --git a/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js index 3c86118d7f8..a438ebf4d75 100644 --- a/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js @@ -846,6 +846,69 @@ const TWO_COMPONENTS_DIFFERENT_FILES: SchemaType = { }, }; +const COMMANDS: SchemaType = { + modules: { + Switch: { + components: { + CommandNativeComponent: { + extendsProps: [ + { + type: 'ReactNativeBuiltInType', + knownTypeName: 'ReactNativeCoreViewProps', + }, + ], + events: [], + props: [], + commands: [ + { + name: 'hotspotUpdate', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + params: [ + { + name: 'x', + typeAnnotation: { + type: 'Int32TypeAnnotation', + }, + }, + { + name: 'y', + typeAnnotation: { + type: 'Int32TypeAnnotation', + }, + }, + ], + }, + }, + { + name: 'scrollTo', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + params: [ + { + name: 'y', + typeAnnotation: { + type: 'Int32TypeAnnotation', + }, + }, + { + name: 'animated', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, +}; + module.exports = { NO_PROPS_NO_EVENTS, INTERFACE_ONLY, @@ -863,4 +926,5 @@ module.exports = { EVENT_NESTED_OBJECT_PROPS, TWO_COMPONENTS_SAME_FILE, TWO_COMPONENTS_DIFFERENT_FILES, + COMMANDS, }; diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap index b936c01f5ea..820373c8c66 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap @@ -63,6 +63,29 @@ namespace react { +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateEventEmitterCpp can generate fixture COMMANDS 1`] = ` +Map { + "EventEmitters.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace facebook { +namespace react { + + + } // namespace react } // namespace facebook ", diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap index f69a4eecc92..22f867ecadd 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap @@ -66,6 +66,30 @@ namespace react { +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateEventEmitterH can generate fixture COMMANDS 1`] = ` +Map { + "EventEmitters.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once + +#include + +namespace facebook { +namespace react { + + + } // namespace react } // namespace facebook ", diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsCpp-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsCpp-test.js.snap index 5fbc03a83e8..16ea3846615 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsCpp-test.js.snap @@ -95,6 +95,35 @@ ColorPropNativeComponentProps::ColorPropNativeComponentProps( } `; +exports[`GeneratePropsCpp can generate fixture COMMANDS 1`] = ` +Map { + "Props.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +namespace facebook { +namespace react { + +CommandNativeComponentProps::CommandNativeComponentProps( + const CommandNativeComponentProps &sourceProps, + const RawProps &rawProps): ViewProps(sourceProps, rawProps) + + + {} + +} // namespace react +} // namespace facebook +", +} +`; + exports[`GeneratePropsCpp can generate fixture ENUM_PROP 1`] = ` Map { "Props.cpp" => " diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsH-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsH-test.js.snap index 9a426bfc1d0..256e4edbddd 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsH-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GeneratePropsH-test.js.snap @@ -157,6 +157,38 @@ class ColorPropNativeComponentProps final : public ViewProps { } `; +exports[`GeneratePropsH can generate fixture COMMANDS 1`] = ` +Map { + "Props.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once + +#include + +namespace facebook { +namespace react { + +class CommandNativeComponentProps final : public ViewProps { + public: + CommandNativeComponentProps() = default; + CommandNativeComponentProps(const CommandNativeComponentProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + +}; + +} // namespace react +} // namespace facebook +", +} +`; + exports[`GeneratePropsH can generate fixture ENUM_PROP 1`] = ` Map { "Props.h" => " diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeCpp-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeCpp-test.js.snap index fce98e6c121..6ed3a9f3d2a 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeCpp-test.js.snap @@ -69,6 +69,29 @@ extern const char ColorPropNativeComponentComponentName[] = \\"ColorPropNativeCo } `; +exports[`GenerateShadowNodeCpp can generate fixture COMMANDS 1`] = ` +Map { + "ShadowNodes.cpp" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace facebook { +namespace react { + +extern const char CommandNativeComponentComponentName[] = \\"CommandNativeComponent\\"; + +} // namespace react +} // namespace facebook +", +} +`; + exports[`GenerateShadowNodeCpp can generate fixture ENUM_PROP 1`] = ` Map { "ShadowNodes.cpp" => " diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeH-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeH-test.js.snap index 0c2430fef91..3c9a387fe6b 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeH-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateShadowNodeH-test.js.snap @@ -99,6 +99,39 @@ using ColorPropNativeComponentShadowNode = ConcreteViewShadowNode< } `; +exports[`GenerateShadowNodeH can generate fixture COMMANDS 1`] = ` +Map { + "ShadowNodes.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +extern const char CommandNativeComponentComponentName[]; + +/* + * \`ShadowNode\` for component. + */ +using CommandNativeComponentShadowNode = ConcreteViewShadowNode< + CommandNativeComponentComponentName, + CommandNativeComponentProps>; + +} // namespace react +} // namespace facebook +", +} +`; + exports[`GenerateShadowNodeH can generate fixture ENUM_PROP 1`] = ` Map { "ShadowNodes.h" => " diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateTests-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateTests-test.js.snap index 8686ab8ce36..d5ee3f10363 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateTests-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateTests-test.js.snap @@ -103,6 +103,34 @@ TEST(ColorPropNativeComponentProps_tintColor, etc) { } `; +exports[`GenerateTests can generate fixture COMMANDS 1`] = ` +Map { + "Tests.cpp" => "/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include +#include + +using namespace facebook::react; + +TEST(CommandNativeComponentProps_DoesNotDie, etc) { + auto propParser = RawPropsParser(); + propParser.prepare(); + auto const &sourceProps = CommandNativeComponentProps(); + auto const &rawProps = RawProps(folly::dynamic::object(\\"xx_invalid_xx\\", \\"xx_invalid_xx\\")); + rawProps.parse(propParser); + CommandNativeComponentProps(sourceProps, rawProps); +}", +} +`; + exports[`GenerateTests can generate fixture ENUM_PROP 1`] = ` Map { "Tests.cpp" => "/** diff --git a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap index 525709ee19e..23d76d6bef3 100644 --- a/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap +++ b/packages/react-native-codegen/src/generators/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap @@ -112,6 +112,58 @@ export default nativeComponentName; } `; +exports[`GenerateViewConfigJs can generate fixture COMMANDS 1`] = ` +Map { + "COMMANDSNativeViewConfig.js" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +const registerGeneratedViewConfig = require('registerGeneratedViewConfig'); +const {UIManager} = require(\\"react-native\\") +const {findNodeHandle} = require(\\"react-native\\") + +const CommandNativeComponentViewConfig = { + uiViewClassName: 'CommandNativeComponent', + validAttributes: {}, +}; + +let nativeComponentName = 'CommandNativeComponent'; + +registerGeneratedViewConfig(nativeComponentName, CommandNativeComponentViewConfig); + +export const __INTERNAL_VIEW_CONFIG = CommandNativeComponentViewConfig; + +export default nativeComponentName; + +export const Commands = { + hotspotUpdate(ref, x, y) { + UIManager.dispatchViewCommand( + findNodeHandle(ref), + UIManager.getViewManagerConfig(\\"CommandNativeComponent\\").Commands.hotspotUpdate, + [x, y] + ); + }, + + scrollTo(ref, y, animated) { + UIManager.dispatchViewCommand( + findNodeHandle(ref), + UIManager.getViewManagerConfig(\\"CommandNativeComponent\\").Commands.scrollTo, + [y, animated] + ); + } +}; +", +} +`; + exports[`GenerateViewConfigJs can generate fixture ENUM_PROP 1`] = ` Map { "ENUM_PROPNativeViewConfig.js" => "