From e51b9cbf7743866c8143c621be7253b36392d028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=A5=E5=B8=8C?= Date: Mon, 27 Jul 2020 17:34:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20code=20generator=20fix?= =?UTF-8?q?=20slot=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code-generator/src/index.ts | 6 +- .../code-generator/src/parser/SchemaParser.ts | 16 ++-- .../component/recore/pageDataSource.ts | 17 ++-- .../plugins/component/recore/pageVmBody.ts | 31 ++++--- .../src/plugins/project/constants.ts | 5 +- packages/code-generator/src/types/core.ts | 39 +++++--- packages/code-generator/src/types/schema.ts | 3 +- .../code-generator/src/utils/compositeType.ts | 71 +++++++------- packages/code-generator/src/utils/jsSlot.ts | 21 +++++ .../code-generator/src/utils/nodeToJSX.ts | 92 +++++++++++++++---- 10 files changed, 202 insertions(+), 99 deletions(-) create mode 100644 packages/code-generator/src/utils/jsSlot.ts diff --git a/packages/code-generator/src/index.ts b/packages/code-generator/src/index.ts index 5d670b5ab..79e527354 100644 --- a/packages/code-generator/src/index.ts +++ b/packages/code-generator/src/index.ts @@ -11,11 +11,7 @@ import createRecoreProjectBuilder from './solutions/recore'; // 引入说明 import { REACT_CHUNK_NAME } from './plugins/component/react/const'; -import { - COMMON_CHUNK_NAME, - CLASS_DEFINE_CHUNK_NAME, - DEFAULT_LINK_AFTER, -} from './const/generator'; +import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from './const/generator'; // 引入通用插件组 import esmodule from './plugins/common/esmodule'; diff --git a/packages/code-generator/src/parser/SchemaParser.ts b/packages/code-generator/src/parser/SchemaParser.ts index 001cb2ca6..57cb01070 100644 --- a/packages/code-generator/src/parser/SchemaParser.ts +++ b/packages/code-generator/src/parser/SchemaParser.ts @@ -5,7 +5,7 @@ import { SUPPORT_SCHEMA_VERSION_LIST } from '../const'; -import { handleChildren } from '../utils/nodeToJSX'; +import { handleSubNodes } from '../utils/nodeToJSX'; import { uniqueArray } from '../utils/common'; import { @@ -198,11 +198,15 @@ class SchemaParser implements ISchemaParser { } getComponentNames(children: ChildNodeType): string[] { - return handleChildren(children, { - node: (i: IComponentNodeItem) => [i.componentName], - }, { - rerun: true, - }); + return handleSubNodes( + children, + { + node: (i: IComponentNodeItem) => [i.componentName], + }, + { + rerun: true, + }, + ); } } diff --git a/packages/code-generator/src/plugins/component/recore/pageDataSource.ts b/packages/code-generator/src/plugins/component/recore/pageDataSource.ts index 87d496aaf..2c580e72f 100644 --- a/packages/code-generator/src/plugins/component/recore/pageDataSource.ts +++ b/packages/code-generator/src/plugins/component/recore/pageDataSource.ts @@ -29,14 +29,11 @@ const pluginFactory: BuilderComponentPluginFactory = () => { const ir = next.ir as IContainerInfo; if (ir.dataSource) { const { dataSource } = ir; - const { - list, - ...rest - } = dataSource; + const { list, ...rest } = dataSource; let attrs: string[] = []; - const extConfigs = Object.keys(rest).map(extConfigName => { + const extConfigs = Object.keys(rest).map((extConfigName) => { const value = (rest as Record)[extConfigName]; const [isString, valueStr] = generateCompositeType(value); return `${extConfigName}: ${isString ? `'${valueStr}'` : valueStr}`; @@ -44,9 +41,13 @@ const pluginFactory: BuilderComponentPluginFactory = () => { attrs = [...attrs, ...extConfigs]; - const listProp = handleStringValueDefault(generateCompositeType(list as unknown as CompositeValue, { - expression: packJsExpression, - })); + const listProp = handleStringValueDefault( + generateCompositeType((list as unknown) as CompositeValue, { + handlers: { + expression: packJsExpression, + }, + }), + ); attrs.push(`list: ${listProp}`); diff --git a/packages/code-generator/src/plugins/component/recore/pageVmBody.ts b/packages/code-generator/src/plugins/component/recore/pageVmBody.ts index 8660ad195..ce09d5e50 100644 --- a/packages/code-generator/src/plugins/component/recore/pageVmBody.ts +++ b/packages/code-generator/src/plugins/component/recore/pageVmBody.ts @@ -5,23 +5,26 @@ import { ICodeStruct, IContainerInfo, IComponentNodeItem, + INodeGeneratorContext, CodePiece, PIECE_TYPE, } 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 { generateExpression } from '../../../utils/jsExpression'; import { generateCompositeType, handleStringValueDefault } from '../../../utils/compositeType'; -const generateGlobalProps = (nodeItem: IComponentNodeItem): CodePiece[] => { - return [{ - value: `{...globalProps.${nodeItem.componentName}}`, - type: PIECE_TYPE.ATTR, - }]; +const generateGlobalProps = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem): CodePiece[] => { + return [ + { + value: `{...globalProps.${nodeItem.componentName}}`, + type: PIECE_TYPE.ATTR, + }, + ]; }; -const generateCtrlLine = (nodeItem: IComponentNodeItem): CodePiece[] => { +const generateCtrlLine = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem): CodePiece[] => { const pieces: CodePiece[] = []; if (nodeItem.loop && nodeItem.loopArgs) { @@ -49,13 +52,13 @@ const generateCtrlLine = (nodeItem: IComponentNodeItem): CodePiece[] => { }; const pluginFactory: BuilderComponentPluginFactory = () => { - const generator = createNodeGenerator({ - string: generateString, - expression: (input) => [generateExpression(input)], - }, [ - generateGlobalProps, - generateCtrlLine, - ]); + const generator = createNodeGenerator( + { + string: generateString, + expression: (input) => [generateExpression(input)], + }, + [generateGlobalProps, generateCtrlLine], + ); const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { diff --git a/packages/code-generator/src/plugins/project/constants.ts b/packages/code-generator/src/plugins/project/constants.ts index 5c02e9711..392a0b96d 100644 --- a/packages/code-generator/src/plugins/project/constants.ts +++ b/packages/code-generator/src/plugins/project/constants.ts @@ -26,10 +26,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { content: ` const constantConfig = ${constantStr}; `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - ], + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], }); next.chunks.push({ diff --git a/packages/code-generator/src/types/core.ts b/packages/code-generator/src/types/core.ts index cf2088f22..489bf17d9 100644 --- a/packages/code-generator/src/types/core.ts +++ b/packages/code-generator/src/types/core.ts @@ -53,17 +53,12 @@ export interface ICodeStruct extends IBaseCodeStruct { chunks: ICodeChunk[]; } -export type BuilderComponentPlugin = ( - initStruct: ICodeStruct, -) => Promise; +export type BuilderComponentPlugin = (initStruct: ICodeStruct) => Promise; export type BuilderComponentPluginFactory = (config?: T) => BuilderComponentPlugin; export interface IChunkBuilder { - run( - ir: any, - initialStructure?: ICodeStruct, - ): Promise<{ chunks: ICodeChunk[][] }>; + run(ir: any, initialStructure?: ICodeStruct): Promise<{ chunks: ICodeChunk[][] }>; getPlugins(): BuilderComponentPlugin[]; addPlugin(plugin: BuilderComponentPlugin): void; } @@ -80,10 +75,7 @@ export interface ICompiledModule { export interface IModuleBuilder { generateModule(input: unknown): Promise; generateModuleCode(schema: IBasicSchema | string): Promise; - linkCodeChunks( - chunks: Record, - fileName: string, - ): IResultFile[]; + linkCodeChunks(chunks: Record, fileName: string): IResultFile[]; addPlugin(plugin: BuilderComponentPlugin): void; } @@ -154,7 +146,7 @@ export enum PIECE_TYPE { ATTR = 'NodeCodePieceAttr', CHILDREN = 'NodeCodePieceChildren', AFTER = 'NodeCodePieceAfter', -}; +} export interface CodePiece { value: string; @@ -168,14 +160,35 @@ export interface HandlerSet { common?: (input: unknown) => T[]; } -export type ExtGeneratorPlugin = (nodeItem: IComponentNodeItem) => CodePiece[]; +export type ExtGeneratorPlugin = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem) => CodePiece[]; export interface INodeGeneratorConfig { nodeTypeMapping?: Record; } +export type NodeGenerator = (nodeItem: IComponentNodeItem) => string; + +export interface INodeGeneratorContext { + generator: NodeGenerator; +} + // export interface InteratorScope { // [$item: string]: string; // $item 默认取值 "item" // [$index: string]: string | number; // $index 默认取值 "index" // __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; +} diff --git a/packages/code-generator/src/types/schema.ts b/packages/code-generator/src/types/schema.ts index 8ce54afa3..e102c54cd 100644 --- a/packages/code-generator/src/types/schema.ts +++ b/packages/code-generator/src/types/schema.ts @@ -33,7 +33,8 @@ export interface IJSFunction { */ export interface IJSSlot { type: 'JSSlot'; - value: IComponentNodeItem; + value: IComponentNodeItem[]; + params?: string[]; [extConfigName: string]: any; } diff --git a/packages/code-generator/src/utils/compositeType.ts b/packages/code-generator/src/utils/compositeType.ts index 8c9011512..0bb6891b8 100644 --- a/packages/code-generator/src/utils/compositeType.ts +++ b/packages/code-generator/src/utils/compositeType.ts @@ -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 { isJsSlot, generateJsSlot } from './jsSlot'; -type CustomHandler = (data: unknown) => string; -interface CustomHandlerSet { - 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(','); +function generateArray(value: CompositeArray, options: CompositeValueGeneratorOptions = {}): string { + const body = value.map((v) => generateUnknownType(v, options)).join(','); return `[${body}]`; } -function generateObject(value: ICompositeObject, handlers: CustomHandlerSet = {}): string { +function generateObject(value: ICompositeObject, options: CompositeValueGeneratorOptions = {}): string { if (isJsExpression(value)) { - if (handlers.expression) { - return handlers.expression(value); + if (options.handlers && options.handlers.expression) { + return options.handlers.expression(value); } return generateExpression(value); } @@ -28,9 +25,16 @@ function generateObject(value: ICompositeObject, handlers: CustomHandlerSet = {} 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) .map((key) => { - const v = generateUnknownType(value[key], handlers); + const v = generateUnknownType(value[key], options); return `${key}: ${v}`; }) .join(',\n'); @@ -38,32 +42,35 @@ function generateObject(value: ICompositeObject, handlers: CustomHandlerSet = {} return `{${body}}`; } -export function generateUnknownType(value: CompositeValue, handlers: CustomHandlerSet = {}): string { +export function generateUnknownType(value: CompositeValue, options: CompositeValueGeneratorOptions = {}): string { if (Array.isArray(value)) { - if (handlers.array) { - return handlers.array(value); + if (options.handlers && options.handlers.array) { + return options.handlers.array(value); } - return generateArray(value as CompositeArray, handlers); + return generateArray(value as CompositeArray, options); } else if (typeof value === 'object') { - if (handlers.object) { - return handlers.object(value); + if (options.handlers && options.handlers.object) { + return options.handlers.object(value); } - return generateObject(value as ICompositeObject, handlers); + return generateObject(value as ICompositeObject, options); } else if (typeof value === 'string') { - if (handlers.string) { - return handlers.string(value); + if (options.handlers && options.handlers.string) { + return options.handlers.string(value); } return `'${value}'`; - } else if (typeof value === 'number' && handlers.number) { - return handlers.number(value); - } else if (typeof value === 'boolean' && handlers.boolean) { - return handlers.boolean(value); + } else if (typeof value === 'number' && options.handlers && options.handlers.number) { + return options.handlers.number(value); + } else if (typeof value === 'boolean' && options.handlers && options.handlers.boolean) { + return options.handlers.boolean(value); } return `${value}`; } -export function generateCompositeType(value: CompositeValue, handlers: CustomHandlerSet = {}): [boolean, string] { - const result = generateUnknownType(value, handlers); +export function generateCompositeType( + value: CompositeValue, + options: CompositeValueGeneratorOptions = {}, +): [boolean, string] { + const result = generateUnknownType(value, options); if (result.substr(0, 1) === "'" && result.substr(-1, 1) === "'") { return [true, result.substring(1, result.length - 1)]; diff --git a/packages/code-generator/src/utils/jsSlot.ts b/packages/code-generator/src/utils/jsSlot.ts new file mode 100644 index 000000000..13f40eef6 --- /dev/null +++ b/packages/code-generator/src/utils/jsSlot.ts @@ -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'); +} diff --git a/packages/code-generator/src/utils/nodeToJSX.ts b/packages/code-generator/src/utils/nodeToJSX.ts index ef6b4617e..28583e7e3 100644 --- a/packages/code-generator/src/utils/nodeToJSX.ts +++ b/packages/code-generator/src/utils/nodeToJSX.ts @@ -9,6 +9,8 @@ import { HandlerSet, ExtGeneratorPlugin, INodeGeneratorConfig, + INodeGeneratorContext, + NodeGenerator, } from '../types'; import { generateCompositeType } from './compositeType'; import { generateExpression, isJsExpression } from './jsExpression'; @@ -20,7 +22,7 @@ const handleChildrenDefaultOptions = { rerun: false, }; -export function handleChildren( +export function handleSubNodes( children: ChildNodeType, handlers: HandlerSet, options?: { @@ -34,7 +36,7 @@ export function handleChildren( if (Array.isArray(children)) { 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') { const handler = handlers.string || handlers.common || noop; return handler(children as string); @@ -45,18 +47,53 @@ export function handleChildren( const handler = handlers.node || handlers.common || noop; let curRes = handler(children as IComponentNodeItem); if (opt.rerun && children.children) { - const childRes = handleChildren(children.children, handlers, opt); + const childRes = handleSubNodes(children.children, handlers, opt); curRes = curRes.concat(childRes || []); } return curRes; } } -export function generateAttr(attrName: string, attrValue: any): CodePiece[] { +export function handleChildren( + ctx: INodeGeneratorContext, + children: ChildNodeType, + handlers: HandlerSet, + 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') { return []; } - const [isString, valueStr] = generateCompositeType(attrValue); + const [isString, valueStr] = generateCompositeType(attrValue, { + nodeGenerator: ctx.generator, + }); return [ { 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; 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; } @@ -81,7 +120,11 @@ function mapNodeName(src: string, mapping: Record): string { return src; } -export function generateBasicNode(nodeItem: IComponentNodeItem, mapping: Record): CodePiece[] { +export function generateBasicNode( + ctx: INodeGeneratorContext, + nodeItem: IComponentNodeItem, + mapping: Record, +): CodePiece[] { const pieces: CodePiece[] = []; pieces.push({ value: mapNodeName(nodeItem.componentName, mapping), @@ -91,7 +134,19 @@ export function generateBasicNode(nodeItem: IComponentNodeItem, mapping: Record< 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[] = []; if (nodeItem.loop && nodeItem.loopArgs) { @@ -112,7 +167,9 @@ export function generateReactCtrlLine(nodeItem: IComponentNodeItem): CodePiece[] } if (nodeItem.condition) { - const [isString, value] = generateCompositeType(nodeItem.condition); + const [isString, value] = generateCompositeType(nodeItem.condition, { + nodeGenerator: ctx.generator, + }); pieces.unshift({ value: `(${isString ? `'${value}'` : value}) && (`, @@ -177,7 +234,7 @@ export function createNodeGenerator( handlers: HandlerSet, plugins: ExtGeneratorPlugin[], cfg?: INodeGeneratorConfig, -) { +): NodeGenerator { let nodeTypeMapping: Record = {}; if (cfg && cfg.nodeTypeMapping) { nodeTypeMapping = cfg.nodeTypeMapping; @@ -185,15 +242,18 @@ export function createNodeGenerator( const generateNode = (nodeItem: IComponentNodeItem): string => { let pieces: CodePiece[] = []; + const ctx: INodeGeneratorContext = { + generator: generateNode, + }; plugins.forEach((p) => { - pieces = pieces.concat(p(nodeItem)); + pieces = pieces.concat(p(ctx, nodeItem)); }); - pieces = pieces.concat(generateBasicNode(nodeItem, nodeTypeMapping)); - pieces = pieces.concat(generateAttrs(nodeItem)); + pieces = pieces.concat(generateBasicNode(ctx, nodeItem, nodeTypeMapping)); + pieces = pieces.concat(generateAttrs(ctx, nodeItem)); if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) { pieces = pieces.concat( - handleChildren(nodeItem.children, handlers).map((l) => ({ + handleChildren(ctx, nodeItem.children, handlers).map((l) => ({ type: PIECE_TYPE.CHILDREN, value: l, })), @@ -210,7 +270,7 @@ export function createNodeGenerator( export const generateString = (input: string) => [input]; -export function createReactNodeGenerator(cfg?: INodeGeneratorConfig) { +export function createReactNodeGenerator(cfg?: INodeGeneratorConfig): NodeGenerator { return createNodeGenerator( { string: generateString,