Skip to content

Commit 967d478

Browse files
elicwhitehramos
authored andcommitted
Require that JS defined Component Attributes match Native ones in dev
Summary: As we move these configs to JS from native, until we have codegen that ensures everything stays up to date, this adds a dev mode check to ensure they are consistent. Reviewed By: yungsters Differential Revision: D9475011 fbshipit-source-id: 9d6f7b6c649229cae569d840eda3d5f7b7aa7cb2
1 parent 499e207 commit 967d478

5 files changed

Lines changed: 230 additions & 176 deletions

File tree

Libraries/Components/View/ViewNativeComponent.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
'use strict';
1212

13+
const AndroidConfig = require('ViewNativeComponentAndroidConfig');
1314
const Platform = require('Platform');
1415
const ReactNative = require('ReactNative');
1516

17+
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
1618
const requireNativeComponent = require('requireNativeComponent');
1719
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
1820

@@ -21,8 +23,11 @@ import type {ViewProps} from 'ViewPropTypes';
2123
type ViewNativeComponentType = Class<ReactNative.NativeComponent<ViewProps>>;
2224

2325
let NativeViewComponent;
26+
if (Platform.OS === 'android') {
27+
if (__DEV__) {
28+
verifyComponentAttributeEquivalence('RCTView', AndroidConfig);
29+
}
2430

25-
if (Platform.OS === 'Android') {
2631
NativeViewComponent = ReactNativeViewConfigRegistry.register('RCTView', () =>
2732
require('ViewNativeComponentAndroidConfig'),
2833
);
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
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+
* @flow
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
14+
const UIManager = require('UIManager');
15+
16+
const insetsDiffer = require('insetsDiffer');
17+
const matricesDiffer = require('matricesDiffer');
18+
const pointsDiffer = require('pointsDiffer');
19+
const processColor = require('processColor');
20+
const resolveAssetSource = require('resolveAssetSource');
21+
const sizesDiffer = require('sizesDiffer');
22+
const invariant = require('fbjs/lib/invariant');
23+
const warning = require('fbjs/lib/warning');
24+
25+
function getNativeComponentAttributes(uiViewClassName: string) {
26+
const viewConfig = UIManager[uiViewClassName];
27+
28+
invariant(
29+
viewConfig != null && viewConfig.NativeProps != null,
30+
'requireNativeComponent: "%s" was not found in the UIManager.',
31+
uiViewClassName,
32+
);
33+
34+
// TODO: This seems like a whole lot of runtime initialization for every
35+
// native component that can be either avoided or simplified.
36+
let {baseModuleName, bubblingEventTypes, directEventTypes} = viewConfig;
37+
let nativeProps = viewConfig.NativeProps;
38+
while (baseModuleName) {
39+
const baseModule = UIManager[baseModuleName];
40+
if (!baseModule) {
41+
warning(false, 'Base module "%s" does not exist', baseModuleName);
42+
baseModuleName = null;
43+
} else {
44+
bubblingEventTypes = {
45+
...baseModule.bubblingEventTypes,
46+
...bubblingEventTypes,
47+
};
48+
directEventTypes = {
49+
...baseModule.directEventTypes,
50+
...directEventTypes,
51+
};
52+
nativeProps = {
53+
...baseModule.NativeProps,
54+
...nativeProps,
55+
};
56+
baseModuleName = baseModule.baseModuleName;
57+
}
58+
}
59+
60+
const validAttributes = {};
61+
62+
for (const key in nativeProps) {
63+
const typeName = nativeProps[key];
64+
const diff = getDifferForType(typeName);
65+
const process = getProcessorForType(typeName);
66+
67+
validAttributes[key] =
68+
diff == null && process == null ? true : {diff, process};
69+
}
70+
71+
// Unfortunately, the current setup declares style properties as top-level
72+
// props. This makes it so we allow style properties in the `style` prop.
73+
// TODO: Move style properties into a `style` prop and disallow them as
74+
// top-level props on the native side.
75+
validAttributes.style = ReactNativeStyleAttributes;
76+
77+
Object.assign(viewConfig, {
78+
uiViewClassName,
79+
validAttributes,
80+
bubblingEventTypes,
81+
directEventTypes,
82+
});
83+
84+
if (!hasAttachedDefaultEventTypes) {
85+
attachDefaultEventTypes(viewConfig);
86+
hasAttachedDefaultEventTypes = true;
87+
}
88+
89+
return viewConfig;
90+
}
91+
92+
// TODO: Figure out how this makes sense. We're using a global boolean to only
93+
// initialize this on the first eagerly initialized native component.
94+
let hasAttachedDefaultEventTypes = false;
95+
function attachDefaultEventTypes(viewConfig: any) {
96+
// This is supported on UIManager platforms (ex: Android),
97+
// as lazy view managers are not implemented for all platforms.
98+
// See [UIManager] for details on constants and implementations.
99+
if (UIManager.ViewManagerNames) {
100+
// Lazy view managers enabled.
101+
viewConfig = merge(viewConfig, UIManager.getDefaultEventTypes());
102+
} else {
103+
viewConfig.bubblingEventTypes = merge(
104+
viewConfig.bubblingEventTypes,
105+
UIManager.genericBubblingEventTypes,
106+
);
107+
viewConfig.directEventTypes = merge(
108+
viewConfig.directEventTypes,
109+
UIManager.genericDirectEventTypes,
110+
);
111+
}
112+
}
113+
114+
// TODO: Figure out how to avoid all this runtime initialization cost.
115+
function merge(destination: ?Object, source: ?Object): ?Object {
116+
if (!source) {
117+
return destination;
118+
}
119+
if (!destination) {
120+
return source;
121+
}
122+
123+
for (const key in source) {
124+
if (!source.hasOwnProperty(key)) {
125+
continue;
126+
}
127+
128+
let sourceValue = source[key];
129+
if (destination.hasOwnProperty(key)) {
130+
const destinationValue = destination[key];
131+
if (
132+
typeof sourceValue === 'object' &&
133+
typeof destinationValue === 'object'
134+
) {
135+
sourceValue = merge(destinationValue, sourceValue);
136+
}
137+
}
138+
destination[key] = sourceValue;
139+
}
140+
return destination;
141+
}
142+
143+
function getDifferForType(
144+
typeName: string,
145+
): ?(prevProp: any, nextProp: any) => boolean {
146+
switch (typeName) {
147+
// iOS Types
148+
case 'CATransform3D':
149+
return matricesDiffer;
150+
case 'CGPoint':
151+
return pointsDiffer;
152+
case 'CGSize':
153+
return sizesDiffer;
154+
case 'UIEdgeInsets':
155+
return insetsDiffer;
156+
// Android Types
157+
// (not yet implemented)
158+
}
159+
return null;
160+
}
161+
162+
function getProcessorForType(typeName: string): ?(nextProp: any) => any {
163+
switch (typeName) {
164+
// iOS Types
165+
case 'CGColor':
166+
case 'UIColor':
167+
return processColor;
168+
case 'CGColorArray':
169+
case 'UIColorArray':
170+
return processColorArray;
171+
case 'CGImage':
172+
case 'UIImage':
173+
case 'RCTImageSource':
174+
return resolveAssetSource;
175+
// Android Types
176+
case 'Color':
177+
return processColor;
178+
case 'ColorArray':
179+
return processColorArray;
180+
}
181+
return null;
182+
}
183+
184+
function processColorArray(colors: ?Array<any>): ?Array<?number> {
185+
return colors == null ? null : colors.map(processColor);
186+
}
187+
188+
module.exports = getNativeComponentAttributes;

0 commit comments

Comments
 (0)