feat: 🎸 code generator fix slot support

This commit is contained in:
春希 2020-07-27 17:34:25 +08:00
parent 9061e4b384
commit e51b9cbf77
10 changed files with 202 additions and 99 deletions

View File

@ -11,11 +11,7 @@ import createRecoreProjectBuilder from './solutions/recore';
// 引入说明 // 引入说明
import { REACT_CHUNK_NAME } from './plugins/component/react/const'; import { REACT_CHUNK_NAME } from './plugins/component/react/const';
import { import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from './const/generator';
COMMON_CHUNK_NAME,
CLASS_DEFINE_CHUNK_NAME,
DEFAULT_LINK_AFTER,
} from './const/generator';
// 引入通用插件组 // 引入通用插件组
import esmodule from './plugins/common/esmodule'; import esmodule from './plugins/common/esmodule';

View File

@ -5,7 +5,7 @@
import { SUPPORT_SCHEMA_VERSION_LIST } from '../const'; import { SUPPORT_SCHEMA_VERSION_LIST } from '../const';
import { handleChildren } from '../utils/nodeToJSX'; import { handleSubNodes } from '../utils/nodeToJSX';
import { uniqueArray } from '../utils/common'; import { uniqueArray } from '../utils/common';
import { import {
@ -198,11 +198,15 @@ class SchemaParser implements ISchemaParser {
} }
getComponentNames(children: ChildNodeType): string[] { getComponentNames(children: ChildNodeType): string[] {
return handleChildren<string>(children, { return handleSubNodes<string>(
children,
{
node: (i: IComponentNodeItem) => [i.componentName], node: (i: IComponentNodeItem) => [i.componentName],
}, { },
{
rerun: true, rerun: true,
}); },
);
} }
} }

View File

@ -29,14 +29,11 @@ const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const ir = next.ir as IContainerInfo; const ir = next.ir as IContainerInfo;
if (ir.dataSource) { if (ir.dataSource) {
const { dataSource } = ir; const { dataSource } = ir;
const { const { list, ...rest } = dataSource;
list,
...rest
} = dataSource;
let attrs: string[] = []; let attrs: string[] = [];
const extConfigs = Object.keys(rest).map(extConfigName => { const extConfigs = Object.keys(rest).map((extConfigName) => {
const value = (rest as Record<string, CompositeValue>)[extConfigName]; const value = (rest as Record<string, CompositeValue>)[extConfigName];
const [isString, valueStr] = generateCompositeType(value); const [isString, valueStr] = generateCompositeType(value);
return `${extConfigName}: ${isString ? `'${valueStr}'` : valueStr}`; return `${extConfigName}: ${isString ? `'${valueStr}'` : valueStr}`;
@ -44,9 +41,13 @@ const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
attrs = [...attrs, ...extConfigs]; attrs = [...attrs, ...extConfigs];
const listProp = handleStringValueDefault(generateCompositeType(list as unknown as CompositeValue, { const listProp = handleStringValueDefault(
generateCompositeType((list as unknown) as CompositeValue, {
handlers: {
expression: packJsExpression, expression: packJsExpression,
})); },
}),
);
attrs.push(`list: ${listProp}`); attrs.push(`list: ${listProp}`);

View File

@ -5,23 +5,26 @@ import {
ICodeStruct, ICodeStruct,
IContainerInfo, IContainerInfo,
IComponentNodeItem, IComponentNodeItem,
INodeGeneratorContext,
CodePiece, CodePiece,
PIECE_TYPE, PIECE_TYPE,
} from '../../../types'; } from '../../../types';
import { COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; import { COMMON_CHUNK_NAME } from '../../../const/generator';
import { createNodeGenerator, generateString } from '../../../utils/nodeToJSX'; import { createNodeGenerator, generateString } from '../../../utils/nodeToJSX';
import { generateExpression } from '../../../utils/jsExpression'; import { generateExpression } from '../../../utils/jsExpression';
import { generateCompositeType, handleStringValueDefault } from '../../../utils/compositeType'; import { generateCompositeType, handleStringValueDefault } from '../../../utils/compositeType';
const generateGlobalProps = (nodeItem: IComponentNodeItem): CodePiece[] => { const generateGlobalProps = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem): CodePiece[] => {
return [{ return [
{
value: `{...globalProps.${nodeItem.componentName}}`, value: `{...globalProps.${nodeItem.componentName}}`,
type: PIECE_TYPE.ATTR, type: PIECE_TYPE.ATTR,
}]; },
];
}; };
const generateCtrlLine = (nodeItem: IComponentNodeItem): CodePiece[] => { const generateCtrlLine = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem): CodePiece[] => {
const pieces: CodePiece[] = []; const pieces: CodePiece[] = [];
if (nodeItem.loop && nodeItem.loopArgs) { if (nodeItem.loop && nodeItem.loopArgs) {
@ -49,13 +52,13 @@ const generateCtrlLine = (nodeItem: IComponentNodeItem): CodePiece[] => {
}; };
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => { const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const generator = createNodeGenerator({ const generator = createNodeGenerator(
{
string: generateString, string: generateString,
expression: (input) => [generateExpression(input)], expression: (input) => [generateExpression(input)],
}, [ },
generateGlobalProps, [generateGlobalProps, generateCtrlLine],
generateCtrlLine, );
]);
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {

View File

@ -26,10 +26,7 @@ const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
content: ` content: `
const constantConfig = ${constantStr}; const constantConfig = ${constantStr};
`, `,
linkAfter: [ linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport],
COMMON_CHUNK_NAME.ExternalDepsImport,
COMMON_CHUNK_NAME.InternalDepsImport,
],
}); });
next.chunks.push({ next.chunks.push({

View File

@ -53,17 +53,12 @@ export interface ICodeStruct extends IBaseCodeStruct {
chunks: ICodeChunk[]; chunks: ICodeChunk[];
} }
export type BuilderComponentPlugin = ( export type BuilderComponentPlugin = (initStruct: ICodeStruct) => Promise<ICodeStruct>;
initStruct: ICodeStruct,
) => Promise<ICodeStruct>;
export type BuilderComponentPluginFactory<T> = (config?: T) => BuilderComponentPlugin; export type BuilderComponentPluginFactory<T> = (config?: T) => BuilderComponentPlugin;
export interface IChunkBuilder { export interface IChunkBuilder {
run( run(ir: any, initialStructure?: ICodeStruct): Promise<{ chunks: ICodeChunk[][] }>;
ir: any,
initialStructure?: ICodeStruct,
): Promise<{ chunks: ICodeChunk[][] }>;
getPlugins(): BuilderComponentPlugin[]; getPlugins(): BuilderComponentPlugin[];
addPlugin(plugin: BuilderComponentPlugin): void; addPlugin(plugin: BuilderComponentPlugin): void;
} }
@ -80,10 +75,7 @@ export interface ICompiledModule {
export interface IModuleBuilder { export interface IModuleBuilder {
generateModule(input: unknown): Promise<ICompiledModule>; generateModule(input: unknown): Promise<ICompiledModule>;
generateModuleCode(schema: IBasicSchema | string): Promise<IResultDir>; generateModuleCode(schema: IBasicSchema | string): Promise<IResultDir>;
linkCodeChunks( linkCodeChunks(chunks: Record<string, ICodeChunk[]>, fileName: string): IResultFile[];
chunks: Record<string, ICodeChunk[]>,
fileName: string,
): IResultFile[];
addPlugin(plugin: BuilderComponentPlugin): void; addPlugin(plugin: BuilderComponentPlugin): void;
} }
@ -154,7 +146,7 @@ export enum PIECE_TYPE {
ATTR = 'NodeCodePieceAttr', ATTR = 'NodeCodePieceAttr',
CHILDREN = 'NodeCodePieceChildren', CHILDREN = 'NodeCodePieceChildren',
AFTER = 'NodeCodePieceAfter', AFTER = 'NodeCodePieceAfter',
}; }
export interface CodePiece { export interface CodePiece {
value: string; value: string;
@ -168,14 +160,35 @@ export interface HandlerSet<T> {
common?: (input: unknown) => T[]; common?: (input: unknown) => T[];
} }
export type ExtGeneratorPlugin = (nodeItem: IComponentNodeItem) => CodePiece[]; export type ExtGeneratorPlugin = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem) => CodePiece[];
export interface INodeGeneratorConfig { export interface INodeGeneratorConfig {
nodeTypeMapping?: Record<string, string>; nodeTypeMapping?: Record<string, string>;
} }
export type NodeGenerator = (nodeItem: IComponentNodeItem) => string;
export interface INodeGeneratorContext {
generator: NodeGenerator;
}
// export interface InteratorScope { // export interface InteratorScope {
// [$item: string]: string; // $item 默认取值 "item" // [$item: string]: string; // $item 默认取值 "item"
// [$index: string]: string | number; // $index 默认取值 "index" // [$index: string]: string | number; // $index 默认取值 "index"
// __proto__: BlockInstance; // __proto__: BlockInstance;
// } // }
export type CompositeValueCustomHandler = (data: unknown) => string;
export interface CompositeValueCustomHandlerSet {
boolean?: CompositeValueCustomHandler;
number?: CompositeValueCustomHandler;
string?: CompositeValueCustomHandler;
array?: CompositeValueCustomHandler;
object?: CompositeValueCustomHandler;
expression?: CompositeValueCustomHandler;
}
export interface CompositeValueGeneratorOptions {
handlers?: CompositeValueCustomHandlerSet;
nodeGenerator?: NodeGenerator;
}

View File

@ -33,7 +33,8 @@ export interface IJSFunction {
*/ */
export interface IJSSlot { export interface IJSSlot {
type: 'JSSlot'; type: 'JSSlot';
value: IComponentNodeItem; value: IComponentNodeItem[];
params?: string[];
[extConfigName: string]: any; [extConfigName: string]: any;
} }

View File

@ -1,25 +1,22 @@
import { CompositeArray, CompositeValue, ICompositeObject } from '../types'; import {
CompositeArray,
CompositeValue,
ICompositeObject,
CompositeValueGeneratorOptions,
CodeGeneratorError,
} from '../types';
import { generateExpression, generateFunction, isJsExpression, isJsFunction } from './jsExpression'; import { generateExpression, generateFunction, isJsExpression, isJsFunction } from './jsExpression';
import { isJsSlot, generateJsSlot } from './jsSlot';
type CustomHandler = (data: unknown) => string; function generateArray(value: CompositeArray, options: CompositeValueGeneratorOptions = {}): string {
interface CustomHandlerSet { const body = value.map((v) => generateUnknownType(v, options)).join(',');
boolean?: CustomHandler;
number?: CustomHandler;
string?: CustomHandler;
array?: CustomHandler;
object?: CustomHandler;
expression?: CustomHandler;
}
function generateArray(value: CompositeArray, handlers: CustomHandlerSet = {}): string {
const body = value.map((v) => generateUnknownType(v, handlers)).join(',');
return `[${body}]`; return `[${body}]`;
} }
function generateObject(value: ICompositeObject, handlers: CustomHandlerSet = {}): string { function generateObject(value: ICompositeObject, options: CompositeValueGeneratorOptions = {}): string {
if (isJsExpression(value)) { if (isJsExpression(value)) {
if (handlers.expression) { if (options.handlers && options.handlers.expression) {
return handlers.expression(value); return options.handlers.expression(value);
} }
return generateExpression(value); return generateExpression(value);
} }
@ -28,9 +25,16 @@ function generateObject(value: ICompositeObject, handlers: CustomHandlerSet = {}
return generateFunction(value, { isArrow: true }); return generateFunction(value, { isArrow: true });
} }
if (isJsSlot(value)) {
if (options.nodeGenerator) {
return generateJsSlot(value, options.nodeGenerator);
}
throw new CodeGeneratorError("Can't find Node Generator");
}
const body = Object.keys(value) const body = Object.keys(value)
.map((key) => { .map((key) => {
const v = generateUnknownType(value[key], handlers); const v = generateUnknownType(value[key], options);
return `${key}: ${v}`; return `${key}: ${v}`;
}) })
.join(',\n'); .join(',\n');
@ -38,32 +42,35 @@ function generateObject(value: ICompositeObject, handlers: CustomHandlerSet = {}
return `{${body}}`; return `{${body}}`;
} }
export function generateUnknownType(value: CompositeValue, handlers: CustomHandlerSet = {}): string { export function generateUnknownType(value: CompositeValue, options: CompositeValueGeneratorOptions = {}): string {
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (handlers.array) { if (options.handlers && options.handlers.array) {
return handlers.array(value); return options.handlers.array(value);
} }
return generateArray(value as CompositeArray, handlers); return generateArray(value as CompositeArray, options);
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
if (handlers.object) { if (options.handlers && options.handlers.object) {
return handlers.object(value); return options.handlers.object(value);
} }
return generateObject(value as ICompositeObject, handlers); return generateObject(value as ICompositeObject, options);
} else if (typeof value === 'string') { } else if (typeof value === 'string') {
if (handlers.string) { if (options.handlers && options.handlers.string) {
return handlers.string(value); return options.handlers.string(value);
} }
return `'${value}'`; return `'${value}'`;
} else if (typeof value === 'number' && handlers.number) { } else if (typeof value === 'number' && options.handlers && options.handlers.number) {
return handlers.number(value); return options.handlers.number(value);
} else if (typeof value === 'boolean' && handlers.boolean) { } else if (typeof value === 'boolean' && options.handlers && options.handlers.boolean) {
return handlers.boolean(value); return options.handlers.boolean(value);
} }
return `${value}`; return `${value}`;
} }
export function generateCompositeType(value: CompositeValue, handlers: CustomHandlerSet = {}): [boolean, string] { export function generateCompositeType(
const result = generateUnknownType(value, handlers); value: CompositeValue,
options: CompositeValueGeneratorOptions = {},
): [boolean, string] {
const result = generateUnknownType(value, options);
if (result.substr(0, 1) === "'" && result.substr(-1, 1) === "'") { if (result.substr(0, 1) === "'" && result.substr(-1, 1) === "'") {
return [true, result.substring(1, result.length - 1)]; return [true, result.substring(1, result.length - 1)];

View File

@ -0,0 +1,21 @@
import { CodeGeneratorError, NodeGenerator, IJSSlot } from '../types';
export function isJsSlot(value: unknown): boolean {
return value && typeof value === 'object' && (value as IJSSlot).type === 'JSSlot';
}
export function generateJsSlot(value: any, generator: NodeGenerator): string {
if (isJsSlot(value)) {
const slotCfg = value as IJSSlot;
if (!slotCfg.value) {
return 'null';
}
const results = slotCfg.value.map((n) => generator(n));
if (results.length === 1) {
return results[0];
}
return `[${results.join(',')}]`;
}
throw new CodeGeneratorError('Not a JSSlot');
}

View File

@ -9,6 +9,8 @@ import {
HandlerSet, HandlerSet,
ExtGeneratorPlugin, ExtGeneratorPlugin,
INodeGeneratorConfig, INodeGeneratorConfig,
INodeGeneratorContext,
NodeGenerator,
} from '../types'; } from '../types';
import { generateCompositeType } from './compositeType'; import { generateCompositeType } from './compositeType';
import { generateExpression, isJsExpression } from './jsExpression'; import { generateExpression, isJsExpression } from './jsExpression';
@ -20,7 +22,7 @@ const handleChildrenDefaultOptions = {
rerun: false, rerun: false,
}; };
export function handleChildren<T>( export function handleSubNodes<T>(
children: ChildNodeType, children: ChildNodeType,
handlers: HandlerSet<T>, handlers: HandlerSet<T>,
options?: { options?: {
@ -34,7 +36,7 @@ export function handleChildren<T>(
if (Array.isArray(children)) { if (Array.isArray(children)) {
const list: ChildNodeItem[] = children as ChildNodeItem[]; const list: ChildNodeItem[] = children as ChildNodeItem[];
return list.map((child) => handleChildren(child, handlers, opt)).reduce((p, c) => p.concat(c), []); return list.map((child) => handleSubNodes(child, handlers, opt)).reduce((p, c) => p.concat(c), []);
} else if (typeof children === 'string') { } else if (typeof children === 'string') {
const handler = handlers.string || handlers.common || noop; const handler = handlers.string || handlers.common || noop;
return handler(children as string); return handler(children as string);
@ -45,18 +47,53 @@ export function handleChildren<T>(
const handler = handlers.node || handlers.common || noop; const handler = handlers.node || handlers.common || noop;
let curRes = handler(children as IComponentNodeItem); let curRes = handler(children as IComponentNodeItem);
if (opt.rerun && children.children) { if (opt.rerun && children.children) {
const childRes = handleChildren(children.children, handlers, opt); const childRes = handleSubNodes(children.children, handlers, opt);
curRes = curRes.concat(childRes || []); curRes = curRes.concat(childRes || []);
} }
return curRes; return curRes;
} }
} }
export function generateAttr(attrName: string, attrValue: any): CodePiece[] { export function handleChildren<T>(
ctx: INodeGeneratorContext,
children: ChildNodeType,
handlers: HandlerSet<T>,
options?: {
rerun?: boolean;
},
): T[] {
const opt = {
...handleChildrenDefaultOptions,
...(options || {}),
};
if (Array.isArray(children)) {
const list: ChildNodeItem[] = children as ChildNodeItem[];
return list.map((child) => handleChildren(ctx, child, handlers, opt)).reduce((p, c) => p.concat(c), []);
} else if (typeof children === 'string') {
const handler = handlers.string || handlers.common || noop;
return handler(children as string);
} else if (isJsExpression(children)) {
const handler = handlers.expression || handlers.common || noop;
return handler(children as IJSExpression);
} else {
const handler = handlers.node || handlers.common || noop;
let curRes = handler(children as IComponentNodeItem);
if (opt.rerun && children.children) {
const childRes = handleChildren(ctx, children.children, handlers, opt);
curRes = curRes.concat(childRes || []);
}
return curRes;
}
}
export function generateAttr(ctx: INodeGeneratorContext, attrName: string, attrValue: any): CodePiece[] {
if (attrName === 'initValue') { if (attrName === 'initValue') {
return []; return [];
} }
const [isString, valueStr] = generateCompositeType(attrValue); const [isString, valueStr] = generateCompositeType(attrValue, {
nodeGenerator: ctx.generator,
});
return [ return [
{ {
value: `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`, value: `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`,
@ -65,11 +102,13 @@ export function generateAttr(attrName: string, attrValue: any): CodePiece[] {
]; ];
} }
export function generateAttrs(nodeItem: IComponentNodeItem): CodePiece[] { export function generateAttrs(ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem): CodePiece[] {
const { props } = nodeItem; const { props } = nodeItem;
let pieces: CodePiece[] = []; let pieces: CodePiece[] = [];
Object.keys(props).forEach((propName: string) => (pieces = pieces.concat(generateAttr(propName, props[propName])))); Object.keys(props).forEach(
(propName: string) => (pieces = pieces.concat(generateAttr(ctx, propName, props[propName]))),
);
return pieces; return pieces;
} }
@ -81,7 +120,11 @@ function mapNodeName(src: string, mapping: Record<string, string>): string {
return src; return src;
} }
export function generateBasicNode(nodeItem: IComponentNodeItem, mapping: Record<string, string>): CodePiece[] { export function generateBasicNode(
ctx: INodeGeneratorContext,
nodeItem: IComponentNodeItem,
mapping: Record<string, string>,
): CodePiece[] {
const pieces: CodePiece[] = []; const pieces: CodePiece[] = [];
pieces.push({ pieces.push({
value: mapNodeName(nodeItem.componentName, mapping), value: mapNodeName(nodeItem.componentName, mapping),
@ -91,7 +134,19 @@ export function generateBasicNode(nodeItem: IComponentNodeItem, mapping: Record<
return pieces; return pieces;
} }
export function generateReactCtrlLine(nodeItem: IComponentNodeItem): CodePiece[] { // TODO: 生成文档
// 为包裹的代码片段生成子上下文,集成父级上下文,并传入子级上下文新增内容。(如果存在多级上下文怎么处理?)
// 创建新的上下文,并从作用域中取对应同名变量塞到作用域里面?
// export function createSubContext() {}
/**
* JSX React loop condition
*
* @export
* @param {IComponentNodeItem} nodeItem UI
* @returns {CodePiece[]}
*/
export function generateReactCtrlLine(ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem): CodePiece[] {
const pieces: CodePiece[] = []; const pieces: CodePiece[] = [];
if (nodeItem.loop && nodeItem.loopArgs) { if (nodeItem.loop && nodeItem.loopArgs) {
@ -112,7 +167,9 @@ export function generateReactCtrlLine(nodeItem: IComponentNodeItem): CodePiece[]
} }
if (nodeItem.condition) { if (nodeItem.condition) {
const [isString, value] = generateCompositeType(nodeItem.condition); const [isString, value] = generateCompositeType(nodeItem.condition, {
nodeGenerator: ctx.generator,
});
pieces.unshift({ pieces.unshift({
value: `(${isString ? `'${value}'` : value}) && (`, value: `(${isString ? `'${value}'` : value}) && (`,
@ -177,7 +234,7 @@ export function createNodeGenerator(
handlers: HandlerSet<string>, handlers: HandlerSet<string>,
plugins: ExtGeneratorPlugin[], plugins: ExtGeneratorPlugin[],
cfg?: INodeGeneratorConfig, cfg?: INodeGeneratorConfig,
) { ): NodeGenerator {
let nodeTypeMapping: Record<string, string> = {}; let nodeTypeMapping: Record<string, string> = {};
if (cfg && cfg.nodeTypeMapping) { if (cfg && cfg.nodeTypeMapping) {
nodeTypeMapping = cfg.nodeTypeMapping; nodeTypeMapping = cfg.nodeTypeMapping;
@ -185,15 +242,18 @@ export function createNodeGenerator(
const generateNode = (nodeItem: IComponentNodeItem): string => { const generateNode = (nodeItem: IComponentNodeItem): string => {
let pieces: CodePiece[] = []; let pieces: CodePiece[] = [];
const ctx: INodeGeneratorContext = {
generator: generateNode,
};
plugins.forEach((p) => { plugins.forEach((p) => {
pieces = pieces.concat(p(nodeItem)); pieces = pieces.concat(p(ctx, nodeItem));
}); });
pieces = pieces.concat(generateBasicNode(nodeItem, nodeTypeMapping)); pieces = pieces.concat(generateBasicNode(ctx, nodeItem, nodeTypeMapping));
pieces = pieces.concat(generateAttrs(nodeItem)); pieces = pieces.concat(generateAttrs(ctx, nodeItem));
if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) { if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) {
pieces = pieces.concat( pieces = pieces.concat(
handleChildren<string>(nodeItem.children, handlers).map((l) => ({ handleChildren<string>(ctx, nodeItem.children, handlers).map((l) => ({
type: PIECE_TYPE.CHILDREN, type: PIECE_TYPE.CHILDREN,
value: l, value: l,
})), })),
@ -210,7 +270,7 @@ export function createNodeGenerator(
export const generateString = (input: string) => [input]; export const generateString = (input: string) => [input];
export function createReactNodeGenerator(cfg?: INodeGeneratorConfig) { export function createReactNodeGenerator(cfg?: INodeGeneratorConfig): NodeGenerator {
return createNodeGenerator( return createNodeGenerator(
{ {
string: generateString, string: generateString,