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
This commit is contained in:
Родитель
499e20766c
Коммит
967d47830b
|
@ -10,9 +10,11 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const AndroidConfig = require('ViewNativeComponentAndroidConfig');
|
||||
const Platform = require('Platform');
|
||||
const ReactNative = require('ReactNative');
|
||||
|
||||
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
|
||||
|
||||
|
@ -21,8 +23,11 @@ import type {ViewProps} from 'ViewPropTypes';
|
|||
type ViewNativeComponentType = Class<ReactNative.NativeComponent<ViewProps>>;
|
||||
|
||||
let NativeViewComponent;
|
||||
if (Platform.OS === 'android') {
|
||||
if (__DEV__) {
|
||||
verifyComponentAttributeEquivalence('RCTView', AndroidConfig);
|
||||
}
|
||||
|
||||
if (Platform.OS === 'Android') {
|
||||
NativeViewComponent = ReactNativeViewConfigRegistry.register('RCTView', () =>
|
||||
require('ViewNativeComponentAndroidConfig'),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
||||
const UIManager = require('UIManager');
|
||||
|
||||
const insetsDiffer = require('insetsDiffer');
|
||||
const matricesDiffer = require('matricesDiffer');
|
||||
const pointsDiffer = require('pointsDiffer');
|
||||
const processColor = require('processColor');
|
||||
const resolveAssetSource = require('resolveAssetSource');
|
||||
const sizesDiffer = require('sizesDiffer');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
||||
function getNativeComponentAttributes(uiViewClassName: string) {
|
||||
const viewConfig = UIManager[uiViewClassName];
|
||||
|
||||
invariant(
|
||||
viewConfig != null && viewConfig.NativeProps != null,
|
||||
'requireNativeComponent: "%s" was not found in the UIManager.',
|
||||
uiViewClassName,
|
||||
);
|
||||
|
||||
// TODO: This seems like a whole lot of runtime initialization for every
|
||||
// native component that can be either avoided or simplified.
|
||||
let {baseModuleName, bubblingEventTypes, directEventTypes} = viewConfig;
|
||||
let nativeProps = viewConfig.NativeProps;
|
||||
while (baseModuleName) {
|
||||
const baseModule = UIManager[baseModuleName];
|
||||
if (!baseModule) {
|
||||
warning(false, 'Base module "%s" does not exist', baseModuleName);
|
||||
baseModuleName = null;
|
||||
} else {
|
||||
bubblingEventTypes = {
|
||||
...baseModule.bubblingEventTypes,
|
||||
...bubblingEventTypes,
|
||||
};
|
||||
directEventTypes = {
|
||||
...baseModule.directEventTypes,
|
||||
...directEventTypes,
|
||||
};
|
||||
nativeProps = {
|
||||
...baseModule.NativeProps,
|
||||
...nativeProps,
|
||||
};
|
||||
baseModuleName = baseModule.baseModuleName;
|
||||
}
|
||||
}
|
||||
|
||||
const validAttributes = {};
|
||||
|
||||
for (const key in nativeProps) {
|
||||
const typeName = nativeProps[key];
|
||||
const diff = getDifferForType(typeName);
|
||||
const process = getProcessorForType(typeName);
|
||||
|
||||
validAttributes[key] =
|
||||
diff == null && process == null ? true : {diff, process};
|
||||
}
|
||||
|
||||
// Unfortunately, the current setup declares style properties as top-level
|
||||
// props. This makes it so we allow style properties in the `style` prop.
|
||||
// TODO: Move style properties into a `style` prop and disallow them as
|
||||
// top-level props on the native side.
|
||||
validAttributes.style = ReactNativeStyleAttributes;
|
||||
|
||||
Object.assign(viewConfig, {
|
||||
uiViewClassName,
|
||||
validAttributes,
|
||||
bubblingEventTypes,
|
||||
directEventTypes,
|
||||
});
|
||||
|
||||
if (!hasAttachedDefaultEventTypes) {
|
||||
attachDefaultEventTypes(viewConfig);
|
||||
hasAttachedDefaultEventTypes = true;
|
||||
}
|
||||
|
||||
return viewConfig;
|
||||
}
|
||||
|
||||
// TODO: Figure out how this makes sense. We're using a global boolean to only
|
||||
// initialize this on the first eagerly initialized native component.
|
||||
let hasAttachedDefaultEventTypes = false;
|
||||
function attachDefaultEventTypes(viewConfig: any) {
|
||||
// This is supported on UIManager platforms (ex: Android),
|
||||
// as lazy view managers are not implemented for all platforms.
|
||||
// See [UIManager] for details on constants and implementations.
|
||||
if (UIManager.ViewManagerNames) {
|
||||
// Lazy view managers enabled.
|
||||
viewConfig = merge(viewConfig, UIManager.getDefaultEventTypes());
|
||||
} else {
|
||||
viewConfig.bubblingEventTypes = merge(
|
||||
viewConfig.bubblingEventTypes,
|
||||
UIManager.genericBubblingEventTypes,
|
||||
);
|
||||
viewConfig.directEventTypes = merge(
|
||||
viewConfig.directEventTypes,
|
||||
UIManager.genericDirectEventTypes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Figure out how to avoid all this runtime initialization cost.
|
||||
function merge(destination: ?Object, source: ?Object): ?Object {
|
||||
if (!source) {
|
||||
return destination;
|
||||
}
|
||||
if (!destination) {
|
||||
return source;
|
||||
}
|
||||
|
||||
for (const key in source) {
|
||||
if (!source.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sourceValue = source[key];
|
||||
if (destination.hasOwnProperty(key)) {
|
||||
const destinationValue = destination[key];
|
||||
if (
|
||||
typeof sourceValue === 'object' &&
|
||||
typeof destinationValue === 'object'
|
||||
) {
|
||||
sourceValue = merge(destinationValue, sourceValue);
|
||||
}
|
||||
}
|
||||
destination[key] = sourceValue;
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
function getDifferForType(
|
||||
typeName: string,
|
||||
): ?(prevProp: any, nextProp: any) => boolean {
|
||||
switch (typeName) {
|
||||
// iOS Types
|
||||
case 'CATransform3D':
|
||||
return matricesDiffer;
|
||||
case 'CGPoint':
|
||||
return pointsDiffer;
|
||||
case 'CGSize':
|
||||
return sizesDiffer;
|
||||
case 'UIEdgeInsets':
|
||||
return insetsDiffer;
|
||||
// Android Types
|
||||
// (not yet implemented)
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getProcessorForType(typeName: string): ?(nextProp: any) => any {
|
||||
switch (typeName) {
|
||||
// iOS Types
|
||||
case 'CGColor':
|
||||
case 'UIColor':
|
||||
return processColor;
|
||||
case 'CGColorArray':
|
||||
case 'UIColorArray':
|
||||
return processColorArray;
|
||||
case 'CGImage':
|
||||
case 'UIImage':
|
||||
case 'RCTImageSource':
|
||||
return resolveAssetSource;
|
||||
// Android Types
|
||||
case 'Color':
|
||||
return processColor;
|
||||
case 'ColorArray':
|
||||
return processColorArray;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function processColorArray(colors: ?Array<any>): ?Array<?number> {
|
||||
return colors == null ? null : colors.map(processColor);
|
||||
}
|
||||
|
||||
module.exports = getNativeComponentAttributes;
|
|
@ -4,24 +4,14 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
||||
const UIManager = require('UIManager');
|
||||
|
||||
const createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
const insetsDiffer = require('insetsDiffer');
|
||||
const matricesDiffer = require('matricesDiffer');
|
||||
const pointsDiffer = require('pointsDiffer');
|
||||
const processColor = require('processColor');
|
||||
const resolveAssetSource = require('resolveAssetSource');
|
||||
const sizesDiffer = require('sizesDiffer');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
const getNativeComponentAttributes = require('getNativeComponentAttributes');
|
||||
|
||||
/**
|
||||
* Creates values that can be used like React components which represent native
|
||||
|
@ -32,167 +22,8 @@ const warning = require('fbjs/lib/warning');
|
|||
*
|
||||
*/
|
||||
const requireNativeComponent = (uiViewClassName: string): string =>
|
||||
createReactNativeComponentClass(uiViewClassName, () => {
|
||||
const viewConfig = UIManager[uiViewClassName];
|
||||
|
||||
invariant(
|
||||
viewConfig != null && viewConfig.NativeProps != null,
|
||||
'requireNativeComponent: "%s" was not found in the UIManager.',
|
||||
uiViewClassName,
|
||||
);
|
||||
|
||||
// TODO: This seems like a whole lot of runtime initialization for every
|
||||
// native component that can be either avoided or simplified.
|
||||
let {baseModuleName, bubblingEventTypes, directEventTypes} = viewConfig;
|
||||
let nativeProps = viewConfig.NativeProps;
|
||||
while (baseModuleName) {
|
||||
const baseModule = UIManager[baseModuleName];
|
||||
if (!baseModule) {
|
||||
warning(false, 'Base module "%s" does not exist', baseModuleName);
|
||||
baseModuleName = null;
|
||||
} else {
|
||||
bubblingEventTypes = {
|
||||
...baseModule.bubblingEventTypes,
|
||||
...bubblingEventTypes,
|
||||
};
|
||||
directEventTypes = {
|
||||
...baseModule.directEventTypes,
|
||||
...directEventTypes,
|
||||
};
|
||||
nativeProps = {
|
||||
...baseModule.NativeProps,
|
||||
...nativeProps,
|
||||
};
|
||||
baseModuleName = baseModule.baseModuleName;
|
||||
}
|
||||
}
|
||||
|
||||
const validAttributes = {};
|
||||
|
||||
for (const key in nativeProps) {
|
||||
const typeName = nativeProps[key];
|
||||
const diff = getDifferForType(typeName);
|
||||
const process = getProcessorForType(typeName);
|
||||
|
||||
validAttributes[key] =
|
||||
diff == null && process == null ? true : {diff, process};
|
||||
}
|
||||
|
||||
// Unfortunately, the current setup declares style properties as top-level
|
||||
// props. This makes it so we allow style properties in the `style` prop.
|
||||
// TODO: Move style properties into a `style` prop and disallow them as
|
||||
// top-level props on the native side.
|
||||
validAttributes.style = ReactNativeStyleAttributes;
|
||||
|
||||
Object.assign(viewConfig, {
|
||||
uiViewClassName,
|
||||
validAttributes,
|
||||
bubblingEventTypes,
|
||||
directEventTypes,
|
||||
});
|
||||
|
||||
if (!hasAttachedDefaultEventTypes) {
|
||||
attachDefaultEventTypes(viewConfig);
|
||||
hasAttachedDefaultEventTypes = true;
|
||||
}
|
||||
|
||||
return viewConfig;
|
||||
});
|
||||
|
||||
// TODO: Figure out how this makes sense. We're using a global boolean to only
|
||||
// initialize this on the first eagerly initialized native component.
|
||||
let hasAttachedDefaultEventTypes = false;
|
||||
function attachDefaultEventTypes(viewConfig: any) {
|
||||
// This is supported on UIManager platforms (ex: Android),
|
||||
// as lazy view managers are not implemented for all platforms.
|
||||
// See [UIManager] for details on constants and implementations.
|
||||
if (UIManager.ViewManagerNames) {
|
||||
// Lazy view managers enabled.
|
||||
viewConfig = merge(viewConfig, UIManager.getDefaultEventTypes());
|
||||
} else {
|
||||
viewConfig.bubblingEventTypes = merge(
|
||||
viewConfig.bubblingEventTypes,
|
||||
UIManager.genericBubblingEventTypes,
|
||||
);
|
||||
viewConfig.directEventTypes = merge(
|
||||
viewConfig.directEventTypes,
|
||||
UIManager.genericDirectEventTypes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Figure out how to avoid all this runtime initialization cost.
|
||||
function merge(destination: ?Object, source: ?Object): ?Object {
|
||||
if (!source) {
|
||||
return destination;
|
||||
}
|
||||
if (!destination) {
|
||||
return source;
|
||||
}
|
||||
|
||||
for (const key in source) {
|
||||
if (!source.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sourceValue = source[key];
|
||||
if (destination.hasOwnProperty(key)) {
|
||||
const destinationValue = destination[key];
|
||||
if (
|
||||
typeof sourceValue === 'object' &&
|
||||
typeof destinationValue === 'object'
|
||||
) {
|
||||
sourceValue = merge(destinationValue, sourceValue);
|
||||
}
|
||||
}
|
||||
destination[key] = sourceValue;
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
function getDifferForType(
|
||||
typeName: string,
|
||||
): ?(prevProp: any, nextProp: any) => boolean {
|
||||
switch (typeName) {
|
||||
// iOS Types
|
||||
case 'CATransform3D':
|
||||
return matricesDiffer;
|
||||
case 'CGPoint':
|
||||
return pointsDiffer;
|
||||
case 'CGSize':
|
||||
return sizesDiffer;
|
||||
case 'UIEdgeInsets':
|
||||
return insetsDiffer;
|
||||
// Android Types
|
||||
// (not yet implemented)
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getProcessorForType(typeName: string): ?(nextProp: any) => any {
|
||||
switch (typeName) {
|
||||
// iOS Types
|
||||
case 'CGColor':
|
||||
case 'UIColor':
|
||||
return processColor;
|
||||
case 'CGColorArray':
|
||||
case 'UIColorArray':
|
||||
return processColorArray;
|
||||
case 'CGImage':
|
||||
case 'UIImage':
|
||||
case 'RCTImageSource':
|
||||
return resolveAssetSource;
|
||||
// Android Types
|
||||
case 'Color':
|
||||
return processColor;
|
||||
case 'ColorArray':
|
||||
return processColorArray;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function processColorArray(colors: ?Array<any>): ?Array<?number> {
|
||||
return colors == null ? null : colors.map(processColor);
|
||||
}
|
||||
createReactNativeComponentClass(uiViewClassName, () =>
|
||||
getNativeComponentAttributes(uiViewClassName),
|
||||
);
|
||||
|
||||
module.exports = requireNativeComponent;
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* 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';
|
||||
|
||||
const deepDiffer = require('deepDiffer');
|
||||
const getNativeComponentAttributes = require('getNativeComponentAttributes');
|
||||
|
||||
import type {ReactNativeBaseComponentViewConfig} from 'ReactNativeTypes';
|
||||
|
||||
function verifyComponentAttributeEquivalence(
|
||||
componentName: string,
|
||||
config: ReactNativeBaseComponentViewConfig<>,
|
||||
) {
|
||||
if (deepDiffer(getNativeComponentAttributes(componentName), config)) {
|
||||
console.error(
|
||||
`${componentName} config in JS does not match config specified by Native`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = verifyComponentAttributeEquivalence;
|
|
@ -90,7 +90,8 @@ jest
|
|||
|
||||
return ReactNative;
|
||||
})
|
||||
.mock('ensureComponentIsNative', () => () => true);
|
||||
.mock('ensureComponentIsNative', () => () => true)
|
||||
.mock('verifyComponentAttributeEquivalence', () => () => {});
|
||||
|
||||
const mockEmptyObject = {};
|
||||
const mockNativeModules = {
|
||||
|
|
Загрузка…
Ссылка в новой задаче