verifyComponentAttributeEquivalence.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * Copyright (c) Facebook, Inc. and its 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. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. const getNativeComponentAttributes = require('../ReactNative/getNativeComponentAttributes');
  12. import ReactNativeViewViewConfig from '../Components/View/ReactNativeViewViewConfig';
  13. import type {ReactNativeBaseComponentViewConfig} from '../Renderer/shims/ReactNativeTypes';
  14. const IGNORED_KEYS = ['transform', 'hitSlop'];
  15. /**
  16. * The purpose of this function is to validate that the view config that
  17. * native exposes for a given view manager is the same as the view config
  18. * that is specified for that view manager in JS.
  19. *
  20. * In order to improve perf, we want to avoid calling into native to get
  21. * the view config when each view manager is used. To do this, we are moving
  22. * the configs to JS. In the future we will use these JS based view configs
  23. * to codegen the view manager on native to ensure they stay in sync without
  24. * this runtime check.
  25. *
  26. * If this function fails, that likely means a change was made to the native
  27. * view manager without updating the JS config as well. Ideally you can make
  28. * that direct change to the JS config. If you don't know what the differences
  29. * are, the best approach I've found is to create a view that prints
  30. * the return value of getNativeComponentAttributes, and then copying that
  31. * text and pasting it back into JS:
  32. * <Text selectable={true}>{JSON.stringify(getNativeComponentAttributes('RCTView'))}</Text>
  33. *
  34. * This is meant to be a stopgap until the time comes when we only have a
  35. * single source of truth. I wonder if this message will still be here two
  36. * years from now...
  37. */
  38. function verifyComponentAttributeEquivalence(
  39. componentName: string,
  40. config: ReactNativeBaseComponentViewConfig<>,
  41. ) {
  42. if (!global.RN$Bridgeless) {
  43. const nativeAttributes = getNativeComponentAttributes(componentName);
  44. ['validAttributes', 'bubblingEventTypes', 'directEventTypes'].forEach(
  45. prop => {
  46. const diffKeys = Object.keys(
  47. lefthandObjectDiff(nativeAttributes[prop], config[prop]),
  48. );
  49. if (diffKeys.length) {
  50. console.error(
  51. `${componentName} generated view config for ${prop} does not match native, missing: ${diffKeys.join(
  52. ' ',
  53. )}`,
  54. );
  55. }
  56. },
  57. );
  58. }
  59. }
  60. export function lefthandObjectDiff(leftObj: Object, rightObj: Object): Object {
  61. const differentKeys = {};
  62. function compare(leftItem, rightItem, key) {
  63. if (typeof leftItem !== typeof rightItem && leftItem != null) {
  64. differentKeys[key] = rightItem;
  65. return;
  66. }
  67. if (typeof leftItem === 'object') {
  68. const objDiff = lefthandObjectDiff(leftItem, rightItem);
  69. if (Object.keys(objDiff).length > 1) {
  70. differentKeys[key] = objDiff;
  71. }
  72. return;
  73. }
  74. if (leftItem !== rightItem) {
  75. differentKeys[key] = rightItem;
  76. return;
  77. }
  78. }
  79. for (const key in leftObj) {
  80. if (IGNORED_KEYS.includes(key)) {
  81. continue;
  82. }
  83. if (!rightObj) {
  84. differentKeys[key] = {};
  85. } else if (leftObj.hasOwnProperty(key)) {
  86. compare(leftObj[key], rightObj[key], key);
  87. }
  88. }
  89. return differentKeys;
  90. }
  91. export function getConfigWithoutViewProps(
  92. viewConfig: ReactNativeBaseComponentViewConfig<>,
  93. propName: string,
  94. ): {...} {
  95. if (!viewConfig[propName]) {
  96. return {};
  97. }
  98. return Object.keys(viewConfig[propName])
  99. .filter(prop => !ReactNativeViewViewConfig[propName][prop])
  100. .reduce((obj, prop) => {
  101. obj[prop] = viewConfig[propName][prop];
  102. return obj;
  103. }, {});
  104. }
  105. export function stringifyViewConfig(viewConfig: any): string {
  106. return JSON.stringify(
  107. viewConfig,
  108. (key, val) => {
  109. if (typeof val === 'function') {
  110. return `ƒ ${val.name}`;
  111. }
  112. return val;
  113. },
  114. 2,
  115. );
  116. }
  117. export default verifyComponentAttributeEquivalence;