amis-rpc-design/node_modules/@react-native/codegen/lib/generators/components/GenerateViewConfigJs.js.flow
2023-10-07 19:42:30 +08:00

489 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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';
import type {
ComponentShape,
EventTypeShape,
PropTypeAnnotation,
} from '../../CodegenSchema';
import type {SchemaType} from '../../CodegenSchema';
const j = require('jscodeshift');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
imports,
componentConfig,
}: {
imports: string,
componentConfig: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @flow
*
* ${'@'}generated by codegen project: GenerateViewConfigJs.js
*/
'use strict';
${imports}
${componentConfig}
`;
// 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: PropTypeAnnotation) {
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'StringEnumTypeAnnotation':
case 'Int32EnumTypeAnnotation':
case 'MixedTypeAnnotation':
return j.literal(true);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/StyleSheet/processColor').default }`;
case 'ImageSourcePrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/Image/resolveAssetSource') }`;
case 'ImageRequestPrimitive':
throw new Error('ImageRequest should not be used in props');
case 'PointPrimitive':
return j.template
.expression`{ diff: require('react-native/Libraries/Utilities/differ/pointsDiffer') }`;
case 'EdgeInsetsPrimitive':
return j.template
.expression`{ diff: require('react-native/Libraries/Utilities/differ/insetsDiffer') }`;
case 'DimensionPrimitive':
return j.literal(true);
default:
(typeAnnotation.name: empty);
throw new Error(
`Received unknown native typeAnnotation: "${typeAnnotation.name}"`,
);
}
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation') {
switch (typeAnnotation.elementType.name) {
case 'ColorPrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/StyleSheet/processColorArray') }`;
case 'ImageSourcePrimitive':
case 'PointPrimitive':
case 'EdgeInsetsPrimitive':
case 'DimensionPrimitive':
return j.literal(true);
default:
throw new Error(
`Received unknown array native typeAnnotation: "${typeAnnotation.elementType.name}"`,
);
}
}
return j.literal(true);
default:
(typeAnnotation: empty);
throw new Error(
`Received unknown typeAnnotation: "${typeAnnotation.type}"`,
);
}
}
const ComponentTemplate = ({
componentName,
paperComponentName,
paperComponentNameDeprecated,
}: {
componentName: string,
paperComponentName: ?string,
paperComponentNameDeprecated: ?string,
}) => {
const nativeComponentName = paperComponentName ?? componentName;
return `
let nativeComponentName = '${nativeComponentName}';
${
paperComponentNameDeprecated != null
? DeprecatedComponentNameCheckTemplate({
componentName,
paperComponentNameDeprecated,
})
: ''
}
export const __INTERNAL_VIEW_CONFIG = VIEW_CONFIG;
export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);
`.trim();
};
// Check whether the native component exists in the app binary.
// Old getViewManagerConfig() checks for the existance of the native Paper view manager. Not available in Bridgeless.
// New hasViewManagerConfig() queries Fabrics native component registry directly.
const DeprecatedComponentNameCheckTemplate = ({
componentName,
paperComponentNameDeprecated,
}: {
componentName: string,
paperComponentNameDeprecated: string,
}) =>
`
if (UIManager.hasViewManagerConfig('${componentName}')) {
nativeComponentName = '${componentName}';
} else if (UIManager.hasViewManagerConfig('${paperComponentNameDeprecated}')) {
nativeComponentName = '${paperComponentNameDeprecated}';
} else {
throw new Error('Failed to find native component for either "${componentName}" or "${paperComponentNameDeprecated}"');
}
`.trim();
// Replicates the behavior of RCTNormalizeInputEventName in RCTEventDispatcher.m
function normalizeInputEventName(name: string) {
if (name.startsWith('on')) {
return name.replace(/^on/, 'top');
} else if (!name.startsWith('top')) {
return `top${name[0].toUpperCase()}${name.slice(1)}`;
}
return name;
}
// Replicates the behavior of viewConfig in RCTComponentData.m
function getValidAttributesForEvents(
events: $ReadOnlyArray<EventTypeShape>,
imports: Set<string>,
) {
imports.add(
"const {ConditionallyIgnoredEventHandlers} = require('react-native/Libraries/NativeComponent/ViewConfigIgnore');",
);
const validAttributes = j.objectExpression(
events.map(eventType => {
return j.property('init', j.identifier(eventType.name), j.literal(true));
}),
);
return j.callExpression(j.identifier('ConditionallyIgnoredEventHandlers'), [
validAttributes,
]);
}
function generateBubblingEventInfo(
event: EventTypeShape,
nameOveride: void | string,
) {
return j.property(
'init',
j.identifier(nameOveride || normalizeInputEventName(event.name)),
j.objectExpression([
j.property(
'init',
j.identifier('phasedRegistrationNames'),
j.objectExpression([
j.property(
'init',
j.identifier('captured'),
j.literal(`${event.name}Capture`),
),
j.property('init', j.identifier('bubbled'), j.literal(event.name)),
]),
),
]),
);
}
function generateDirectEventInfo(
event: EventTypeShape,
nameOveride: void | string,
) {
return j.property(
'init',
j.identifier(nameOveride || normalizeInputEventName(event.name)),
j.objectExpression([
j.property(
'init',
j.identifier('registrationName'),
j.literal(event.name),
),
]),
);
}
function buildViewConfig(
schema: SchemaType,
componentName: string,
component: ComponentShape,
imports: Set<string>,
) {
const componentProps = component.props;
const componentEvents = component.events;
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
"const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry');",
);
return;
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
});
const validAttributes = j.objectExpression([
...componentProps.map(schemaProp => {
return j.property(
'init',
j.identifier(schemaProp.name),
getReactDiffProcessValue(schemaProp.typeAnnotation),
);
}),
...(componentEvents.length > 0
? [
j.spreadProperty(
getValidAttributesForEvents(componentEvents, imports),
),
]
: []),
]);
const bubblingEventNames = component.events
.filter(event => event.bubblingType === 'bubble')
.reduce((bubblingEvents: Array<any>, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
bubblingEvents.push(
generateBubblingEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
bubblingEvents.push(generateBubblingEventInfo(event));
}
return bubblingEvents;
}, []);
const bubblingEvents =
bubblingEventNames.length > 0
? j.property(
'init',
j.identifier('bubblingEventTypes'),
j.objectExpression(bubblingEventNames),
)
: null;
const directEventNames = component.events
.filter(event => event.bubblingType === 'direct')
.reduce((directEvents: Array<any>, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
directEvents.push(
generateDirectEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
directEvents.push(generateDirectEventInfo(event));
}
return directEvents;
}, []);
const directEvents =
directEventNames.length > 0
? j.property(
'init',
j.identifier('directEventTypes'),
j.objectExpression(directEventNames),
)
: null;
const properties = [
j.property(
'init',
j.identifier('uiViewClassName'),
j.literal(componentName),
),
bubblingEvents,
directEvents,
j.property('init', j.identifier('validAttributes'), validAttributes),
].filter(Boolean);
return j.objectExpression(properties);
}
function buildCommands(
schema: SchemaType,
componentName: string,
component: ComponentShape,
imports: Set<string>,
) {
const commands = component.commands;
if (commands.length === 0) {
return null;
}
imports.add(
'const {dispatchCommand} = require("react-native/Libraries/ReactNative/RendererProxy");',
);
const properties = commands.map(command => {
const commandName = command.name;
const params = command.typeAnnotation.params;
const commandNameLiteral = j.literal(commandName);
const commandNameIdentifier = j.identifier(commandName);
const arrayParams = j.arrayExpression(
params.map(param => {
return j.identifier(param.name);
}),
);
const expression = j.template
.expression`dispatchCommand(ref, ${commandNameLiteral}, ${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 {
const fileName = `${libraryName}NativeViewConfig.js`;
const imports: Set<string> = new Set();
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
return Object.keys(components)
.map((componentName: string) => {
const component = components[componentName];
if (component.paperComponentNameDeprecated) {
imports.add(UIMANAGER_IMPORT);
}
const replacedTemplate = ComponentTemplate({
componentName,
paperComponentName: component.paperComponentName,
paperComponentNameDeprecated:
component.paperComponentNameDeprecated,
});
const replacedSourceRoot = j.withParser('flow')(replacedTemplate);
const paperComponentName =
component.paperComponentName ?? componentName;
replacedSourceRoot
.find(j.Identifier, {
name: 'VIEW_CONFIG',
})
.replaceWith(
buildViewConfig(
schema,
paperComponentName,
component,
imports,
),
);
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;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentConfig: moduleResults,
imports: Array.from(imports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
} catch (error) {
console.error(`\nError parsing schema for ${libraryName}\n`);
console.error(JSON.stringify(schema));
throw error;
}
},
};