refactor: 💡 node2jsx function

This commit is contained in:
春希 2020-08-16 20:56:37 +08:00
parent 95d67c1d99
commit e689d7f341
6 changed files with 165 additions and 211 deletions

View File

@ -13,7 +13,7 @@ import {
import { RAX_CHUNK_NAME } from './const'; import { RAX_CHUNK_NAME } from './const';
import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { COMMON_CHUNK_NAME } from '../../../const/generator';
import { createNodeGenerator, generateReactCtrlLine, generateString } from '../../../utils/nodeToJSX'; import { createNodeGenerator, generateReactCtrlLine } from '../../../utils/nodeToJSX';
import { generateExpression } from '../../../utils/jsExpression'; import { generateExpression } from '../../../utils/jsExpression';
type PluginConfig = { type PluginConfig = {
@ -62,20 +62,15 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk)); next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk));
// 创建代码生成器 // 创建代码生成器
const generator = createNodeGenerator( const generator = createNodeGenerator({
{ handlers: {
string: generateString,
expression: (input) => [handlers.expression(input)],
function: (input) => [handlers.function(input)],
},
[generateReactCtrlLine],
{
expression: (input: JSExpression) => (isJSExpression(input) ? handlers.expression(input) : ''), expression: (input: JSExpression) => (isJSExpression(input) ? handlers.expression(input) : ''),
function: (input: JSFunction) => (isJSFunction(input) ? handlers.function(input) : ''), function: (input: JSFunction) => (isJSFunction(input) ? handlers.function(input) : ''),
loopDataExpr: (input: string) => (typeof input === 'string' ? transformers.transformLoopExpr(input) : ''), loopDataExpr: (input: string) => (typeof input === 'string' ? transformers.transformLoopExpr(input) : ''),
tagName: mapComponentNameToAliasOrKeepIt, tagName: mapComponentNameToAliasOrKeepIt,
}, },
); plugins: [generateReactCtrlLine],
});
// 生成 JSX 代码 // 生成 JSX 代码
const jsxContent = generator(ir); const jsxContent = generator(ir);

View File

@ -23,7 +23,13 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
...config, ...config,
}; };
const generator = createReactNodeGenerator({ nodeTypeMapping: cfg.nodeTypeMapping }); const { nodeTypeMapping } = cfg;
const generator = createReactNodeGenerator({
handlers: {
tagName: (v) => nodeTypeMapping[v] || v,
},
});
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {

View File

@ -12,8 +12,7 @@ import {
} from '../../../types'; } from '../../../types';
import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { COMMON_CHUNK_NAME } from '../../../const/generator';
import { createNodeGenerator, generateString } from '../../../utils/nodeToJSX'; import { createNodeGenerator } from '../../../utils/nodeToJSX';
import { generateExpression } from '../../../utils/jsExpression';
import { generateCompositeType } from '../../../utils/compositeType'; import { generateCompositeType } from '../../../utils/compositeType';
const generateGlobalProps = (ctx: INodeGeneratorContext, nodeItem: NodeSchema): CodePiece[] => { const generateGlobalProps = (ctx: INodeGeneratorContext, nodeItem: NodeSchema): CodePiece[] => {
@ -53,13 +52,9 @@ const generateCtrlLine = (ctx: INodeGeneratorContext, nodeItem: NodeSchema): Cod
}; };
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => { const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const generator = createNodeGenerator( const generator = createNodeGenerator({
{ plugins: [generateGlobalProps, generateCtrlLine],
string: generateString, });
expression: (input) => [generateExpression(input)],
},
[generateGlobalProps, generateCtrlLine],
);
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {

View File

@ -1,15 +1,11 @@
import { import {
ResultDir, ResultDir,
ResultFile, ResultFile,
NodeData, NodeDataType,
NodeSchema, NodeSchema,
ProjectSchema, ProjectSchema,
JSExpression, JSExpression,
JSFunction, JSFunction,
CompositeArray,
CompositeObject,
JSONArray,
JSONObject,
JSSlot, JSSlot,
} from '@ali/lowcode-types'; } from '@ali/lowcode-types';
@ -149,45 +145,38 @@ export interface CodePiece {
type: PIECE_TYPE; type: PIECE_TYPE;
} }
// TODO: 这个 HandlerSet 和 CustomHandlerSet 为啥定义还不一样?
export interface HandlerSet<T> { export interface HandlerSet<T> {
string?: (input: string) => T[]; string?: (input: string) => T;
expression?: (input: JSExpression) => T[]; boolean?: (input: boolean) => T;
function?: (input: JSFunction) => T[]; number?: (input: number) => T;
node?: (input: NodeSchema) => T[]; expression?: (input: JSExpression) => T;
common?: (input: unknown) => T[]; function?: (input: JSFunction) => T;
slot?: (input: JSSlot) => T;
node?: (input: NodeSchema) => T;
array?: (input: any[]) => T;
object?: (input: object) => T;
common?: (input: unknown) => T;
tagName?: (input: string) => T;
} }
export type ExtGeneratorPlugin = ( export type ExtGeneratorPlugin = (ctx: INodeGeneratorContext, nodeItem: NodeSchema) => CodePiece[];
ctx: INodeGeneratorContext,
nodeItem: NodeSchema,
handlers: CustomHandlerSet,
) => CodePiece[];
export interface INodeGeneratorConfig { export type NodeGeneratorConfig = {
nodeTypeMapping?: Record<string, string>; handlers?: HandlerSet<string>;
} plugins?: ExtGeneratorPlugin[];
};
export type NodeGenerator = (nodeItem: NodeData) => string; export type NodeGenerator = (nodeItem: NodeDataType) => string;
export interface INodeGeneratorContext { export interface INodeGeneratorContext {
handlers: HandlerSet<string>;
generator: NodeGenerator; generator: NodeGenerator;
} }
export type CompositeValueCustomHandler = (data: unknown) => string; export type CompositeValueCustomHandler = (data: unknown) => string;
export type CompositeTypeContainerHandler = (value: string) => string;
export interface CompositeValueCustomHandlerSet {
boolean?: CompositeValueCustomHandler;
number?: CompositeValueCustomHandler;
string?: CompositeValueCustomHandler;
array?: CompositeValueCustomHandler;
object?: CompositeValueCustomHandler;
expression?: CompositeValueCustomHandler;
function?: CompositeValueCustomHandler;
}
export type CompositeValueGeneratorOptions = { export type CompositeValueGeneratorOptions = {
handlers?: CompositeValueCustomHandlerSet; handlers?: HandlerSet<string>;
containerHandler?: (value: string, isString: boolean, valStr: string) => string; containerHandler?: (value: string, isString: boolean, valStr: string) => string;
nodeGenerator?: NodeGenerator; nodeGenerator?: NodeGenerator;
}; };

View File

@ -13,7 +13,7 @@ import { generateJsSlot } from './jsSlot';
import { isValidIdentifier } from './validate'; import { isValidIdentifier } from './validate';
import { camelize } from './common'; import { camelize } from './common';
import { CompositeValueGeneratorOptions, CompositeTypeContainerHandler, CodeGeneratorError } from '../types'; import { CompositeValueGeneratorOptions, CodeGeneratorError } from '../types';
function generateArray(value: CompositeArray, options: CompositeValueGeneratorOptions = {}): string { function generateArray(value: CompositeArray, options: CompositeValueGeneratorOptions = {}): string {
const body = value.map((v) => generateUnknownType(v, options)).join(','); const body = value.map((v) => generateUnknownType(v, options)).join(',');
@ -110,12 +110,12 @@ function generateUnknownType(value: CompositeValue, options: CompositeValueGener
return JSON.stringify(value); return JSON.stringify(value);
} }
const defaultContainer: CompositeTypeContainerHandler = (v: string) => v; const defaultContainer = (v: string) => v;
export function generateCompositeType(value: CompositeValue, options: CompositeValueGeneratorOptions = {}): string { export function generateCompositeType(value: CompositeValue, options: CompositeValueGeneratorOptions = {}): string {
const isStringType = _.isString(value); const isStringType = _.isString(value);
const result = generateUnknownType(value, options); const result = generateUnknownType(value, options);
const handler: CompositeTypeContainerHandler = options.containerHandler || defaultContainer; const handler = options.containerHandler || defaultContainer;
if (isStringType && result.length >= 2) { if (isStringType && result.length >= 2) {
return handler(result, true, result.substring(1, result.length - 1)); return handler(result, true, result.substring(1, result.length - 1));

View File

@ -4,10 +4,12 @@ import {
JSExpression, JSExpression,
NodeData, NodeData,
NodeSchema, NodeSchema,
isNodeSchema,
PropsMap, PropsMap,
isJSExpression, isJSExpression,
isJSSlot, isJSSlot,
isDOMText, isDOMText,
NodeDataType,
} from '@ali/lowcode-types'; } from '@ali/lowcode-types';
import { import {
@ -18,11 +20,10 @@ import {
NodeGenerator, NodeGenerator,
ExtGeneratorPlugin, ExtGeneratorPlugin,
INodeGeneratorContext, INodeGeneratorContext,
INodeGeneratorConfig, NodeGeneratorConfig,
} from '../types'; } from '../types';
import { generateCompositeType } from './compositeType'; import { generateCompositeType } from './compositeType';
import { generateExpression } from './jsExpression';
// tslint:disable-next-line: no-empty // tslint:disable-next-line: no-empty
const noop = () => []; const noop = () => [];
@ -75,40 +76,6 @@ export function handleSubNodes<T>(
} }
} }
export function handleChildren<T>(
ctx: INodeGeneratorContext,
children: unknown,
handlers: HandlerSet<T>,
options?: {
rerun?: boolean;
},
): T[] {
const opt = {
...handleChildrenDefaultOptions,
...(options || {}),
};
if (Array.isArray(children)) {
const list: NodeData[] = children as NodeData[];
return list.map((child) => handleChildren(ctx, child, handlers, opt)).reduce((p, c) => p.concat(c), []);
} else if (isDOMText(children)) {
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 JSExpression);
} else {
const handler = handlers.node || handlers.common || noop;
const child = children as NodeSchema;
let curRes = handler(child);
if (opt.rerun && child.children) {
const childRes = handleChildren(ctx, child.children, handlers, opt);
curRes = curRes.concat(childRes || []);
}
return curRes;
}
}
export function generateAttr(ctx: INodeGeneratorContext, attrName: string, attrValue: any): CodePiece[] { export function generateAttr(ctx: INodeGeneratorContext, attrName: string, attrValue: any): CodePiece[] {
if (attrName === 'initValue') { if (attrName === 'initValue') {
return []; return [];
@ -149,102 +116,24 @@ export function generateAttrs(ctx: INodeGeneratorContext, nodeItem: NodeSchema):
return pieces; return pieces;
} }
function mapNodeName(src: string, mapping: Record<string, string>): string { export function generateBasicNode(ctx: INodeGeneratorContext, nodeItem: NodeSchema): CodePiece[] {
if (mapping[src]) {
return mapping[src];
}
return src;
}
export function generateBasicNode(
ctx: INodeGeneratorContext,
nodeItem: NodeSchema,
mapping: Record<string, string>,
): CodePiece[] {
const pieces: CodePiece[] = []; const pieces: CodePiece[] = [];
const tagName = (ctx.handlers.tagName || _.identity)(nodeItem.componentName);
pieces.push({ pieces.push({
value: mapNodeName(nodeItem.componentName, mapping), value: tagName || '', // FIXME: type detection error
type: PIECE_TYPE.TAG, type: PIECE_TYPE.TAG,
}); });
return pieces; return pieces;
} }
// TODO: 生成文档 export function linkPieces(pieces: CodePiece[]): string {
// 为包裹的代码片段生成子上下文,集成父级上下文,并传入子级上下文新增内容。(如果存在多级上下文怎么处理?)
// 创建新的上下文,并从作用域中取对应同名变量塞到作用域里面?
// export function createSubContext() {}
/**
* JSX React loop condition
*
* @export
* @param {NodeSchema} nodeItem UI
* @returns {CodePiece[]}
*/
export function generateReactCtrlLine(ctx: INodeGeneratorContext, nodeItem: NodeSchema): CodePiece[] {
const pieces: CodePiece[] = [];
if (nodeItem.loop) {
const loopItemName = nodeItem.loopArgs?.[0] || 'item';
const loopIndexName = nodeItem.loopArgs?.[1] || 'index';
const loopDataExpr = (handlers.loopDataExpr || _.identity)(
isJSExpression(nodeItem.loop) ? `(${nodeItem.loop.value})` : `(${JSON.stringify(nodeItem.loop)})`,
);
pieces.unshift({
value: `${loopDataExpr}.map((${loopItemName}, ${loopIndexName}) => (`,
type: PIECE_TYPE.BEFORE,
});
pieces.push({
value: '))',
type: PIECE_TYPE.AFTER,
});
}
if (nodeItem.condition) {
const value = generateCompositeType(nodeItem.condition, {
nodeGenerator: ctx.generator,
});
const conditionExpr = (handlers.conditionExpr || _.identity)(value);
pieces.unshift({
value: `(${conditionExpr}) && (`,
type: PIECE_TYPE.BEFORE,
});
pieces.push({
value: ')',
type: PIECE_TYPE.AFTER,
});
}
if (nodeItem.condition || nodeItem.loop) {
pieces.unshift({
value: '{',
type: PIECE_TYPE.BEFORE,
});
pieces.push({
value: '}',
type: PIECE_TYPE.AFTER,
});
}
return pieces;
}
export function linkPieces(pieces: CodePiece[], handlers: CustomHandlerSet): string {
const tagsPieces = pieces.filter((p) => p.type === PIECE_TYPE.TAG); const tagsPieces = pieces.filter((p) => p.type === PIECE_TYPE.TAG);
if (tagsPieces.length !== 1) { if (tagsPieces.length !== 1) {
throw new CodeGeneratorError('One node only need one tag define'); throw new CodeGeneratorError('One node only need one tag define');
} }
const tagName = tagsPieces[0].value;
const tagName = (handlers.tagName || _.identity)(tagsPieces[0].value);
const beforeParts = pieces const beforeParts = pieces
.filter((p) => p.type === PIECE_TYPE.BEFORE) .filter((p) => p.type === PIECE_TYPE.BEFORE)
@ -275,26 +164,13 @@ export function linkPieces(pieces: CodePiece[], handlers: CustomHandlerSet): str
return `${beforeParts}<${tagName}${attrsParts} />${afterParts}`; return `${beforeParts}<${tagName}${attrsParts} />${afterParts}`;
} }
export function createNodeGenerator( export function generateNodeSchema(nodeItem: NodeSchema, ctx: INodeGeneratorContext): string {
handlers: HandlerSet<string>,
plugins: ExtGeneratorPlugin[],
cfg?: INodeGeneratorConfig,
): NodeGenerator {
let nodeTypeMapping: Record<string, string> = {};
if (cfg && cfg.nodeTypeMapping) {
nodeTypeMapping = cfg.nodeTypeMapping;
}
const generateNode = (nodeItem: NodeSchema): string => {
let pieces: CodePiece[] = []; let pieces: CodePiece[] = [];
const ctx: INodeGeneratorContext = {
generator: generateNode,
};
plugins.forEach((p) => { plugins.forEach((p) => {
pieces = pieces.concat(p(ctx, nodeItem)); pieces = pieces.concat(p(ctx, nodeItem));
}); });
pieces = pieces.concat(generateBasicNode(ctx, nodeItem, nodeTypeMapping)); pieces = pieces.concat(generateBasicNode(ctx, nodeItem));
pieces = pieces.concat(generateAttrs(ctx, 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(
@ -305,23 +181,116 @@ export function createNodeGenerator(
); );
} }
return linkPieces(pieces, customHandlers); return linkPieces(pieces);
}
// TODO: 生成文档
// 为包裹的代码片段生成子上下文,集成父级上下文,并传入子级上下文新增内容。(如果存在多级上下文怎么处理?)
// 创建新的上下文,并从作用域中取对应同名变量塞到作用域里面?
// export function createSubContext() {}
/**
* JSX React loop condition
*
* @export
* @param {NodeSchema} nodeItem UI
* @returns {CodePiece[]}
*/
export function generateReactCtrlLine(ctx: INodeGeneratorContext, nodeItem: NodeSchema): CodePiece[] {
const pieces: CodePiece[] = [];
if (nodeItem.loop) {
const loopItemName = nodeItem.loopArgs?.[0] || 'item';
const loopIndexName = nodeItem.loopArgs?.[1] || 'index';
const loopDataExpr = (ctx.handlers.loopDataExpr || _.identity)(
isJSExpression(nodeItem.loop) ? `(${nodeItem.loop.value})` : `(${JSON.stringify(nodeItem.loop)})`,
);
pieces.unshift({
value: `${loopDataExpr}.map((${loopItemName}, ${loopIndexName}) => (`,
type: PIECE_TYPE.BEFORE,
});
pieces.push({
value: '))',
type: PIECE_TYPE.AFTER,
});
}
if (nodeItem.condition) {
const value = generateCompositeType(nodeItem.condition, {
nodeGenerator: ctx.generator,
});
const conditionExpr = (ctx.handlers.conditionExpr || _.identity)(value);
pieces.unshift({
value: `(${conditionExpr}) && (`,
type: PIECE_TYPE.BEFORE,
});
pieces.push({
value: ')',
type: PIECE_TYPE.AFTER,
});
}
if (nodeItem.condition || nodeItem.loop) {
pieces.unshift({
value: '{',
type: PIECE_TYPE.BEFORE,
});
pieces.push({
value: '}',
type: PIECE_TYPE.AFTER,
});
}
return pieces;
}
const handleArray = (v: string[]) => v.join('');
export function createNodeGenerator(cfg: NodeGeneratorConfig = {}): NodeGenerator {
let ctx: INodeGeneratorContext = { handlers: {}, generator: () => '' };
const generateNode = (nodeItem: NodeDataType): string => {
if (_.isArray(nodeItem)) {
const resList = nodeItem.map((n) => generateNode(n));
return (cfg?.handlers?.array || handleArray)(resList);
}
if (isNodeSchema(nodeItem)) {
if (cfg?.handlers?.node) {
// TODO: children 的处理是否拆出来作为公用
return cfg.handlers.node(nodeItem);
}
return generateNodeSchema(nodeItem, ctx);
}
return generateCompositeType(nodeItem, {
handlers: cfg.handlers,
// FIXME: 这里和 children 类型的嵌套逻辑需要再思考一下
// containerHandler: (value: string, isString: boolean, valStr: string) => (isString ? valStr : value),
nodeGenerator: generateNode,
});
}; };
handlers.node = (input: NodeSchema) => [generateNode(input)]; ctx = {
handlers: cfg?.handlers || {},
generator: generateNode,
};
return generateNode; return generateNode;
} }
export const generateString = (input: string) => [input]; // TODO: 需要一个 merge config 的方法。
export function createReactNodeGenerator(cfg?: NodeGeneratorConfig): NodeGenerator {
export function createReactNodeGenerator(cfg?: INodeGeneratorConfig): NodeGenerator { return createNodeGenerator({
return createNodeGenerator( plugins: [generateReactCtrlLine],
{ ...cfg,
string: generateString, });
expression: (input) => [generateExpression(input)],
},
[generateReactCtrlLine],
cfg,
);
} }