mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-13 09:41:57 +00:00
refactor: 💡 node2jsx function
This commit is contained in:
parent
95d67c1d99
commit
e689d7f341
@ -13,7 +13,7 @@ import {
|
||||
import { RAX_CHUNK_NAME } from './const';
|
||||
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';
|
||||
|
||||
type PluginConfig = {
|
||||
@ -62,20 +62,15 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
|
||||
next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk));
|
||||
|
||||
// 创建代码生成器
|
||||
const generator = createNodeGenerator(
|
||||
{
|
||||
string: generateString,
|
||||
expression: (input) => [handlers.expression(input)],
|
||||
function: (input) => [handlers.function(input)],
|
||||
},
|
||||
[generateReactCtrlLine],
|
||||
{
|
||||
const generator = createNodeGenerator({
|
||||
handlers: {
|
||||
expression: (input: JSExpression) => (isJSExpression(input) ? handlers.expression(input) : ''),
|
||||
function: (input: JSFunction) => (isJSFunction(input) ? handlers.function(input) : ''),
|
||||
loopDataExpr: (input: string) => (typeof input === 'string' ? transformers.transformLoopExpr(input) : ''),
|
||||
tagName: mapComponentNameToAliasOrKeepIt,
|
||||
},
|
||||
);
|
||||
plugins: [generateReactCtrlLine],
|
||||
});
|
||||
|
||||
// 生成 JSX 代码
|
||||
const jsxContent = generator(ir);
|
||||
|
||||
@ -23,7 +23,13 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (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 next: ICodeStruct = {
|
||||
|
||||
@ -12,8 +12,7 @@ import {
|
||||
} from '../../../types';
|
||||
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||
|
||||
import { createNodeGenerator, generateString } from '../../../utils/nodeToJSX';
|
||||
import { generateExpression } from '../../../utils/jsExpression';
|
||||
import { createNodeGenerator } from '../../../utils/nodeToJSX';
|
||||
import { generateCompositeType } from '../../../utils/compositeType';
|
||||
|
||||
const generateGlobalProps = (ctx: INodeGeneratorContext, nodeItem: NodeSchema): CodePiece[] => {
|
||||
@ -53,13 +52,9 @@ const generateCtrlLine = (ctx: INodeGeneratorContext, nodeItem: NodeSchema): Cod
|
||||
};
|
||||
|
||||
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||
const generator = createNodeGenerator(
|
||||
{
|
||||
string: generateString,
|
||||
expression: (input) => [generateExpression(input)],
|
||||
},
|
||||
[generateGlobalProps, generateCtrlLine],
|
||||
);
|
||||
const generator = createNodeGenerator({
|
||||
plugins: [generateGlobalProps, generateCtrlLine],
|
||||
});
|
||||
|
||||
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||
const next: ICodeStruct = {
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
import {
|
||||
ResultDir,
|
||||
ResultFile,
|
||||
NodeData,
|
||||
NodeDataType,
|
||||
NodeSchema,
|
||||
ProjectSchema,
|
||||
JSExpression,
|
||||
JSFunction,
|
||||
CompositeArray,
|
||||
CompositeObject,
|
||||
JSONArray,
|
||||
JSONObject,
|
||||
JSSlot,
|
||||
} from '@ali/lowcode-types';
|
||||
|
||||
@ -149,45 +145,38 @@ export interface CodePiece {
|
||||
type: PIECE_TYPE;
|
||||
}
|
||||
|
||||
// TODO: 这个 HandlerSet 和 CustomHandlerSet 为啥定义还不一样?
|
||||
export interface HandlerSet<T> {
|
||||
string?: (input: string) => T[];
|
||||
expression?: (input: JSExpression) => T[];
|
||||
function?: (input: JSFunction) => T[];
|
||||
node?: (input: NodeSchema) => T[];
|
||||
common?: (input: unknown) => T[];
|
||||
string?: (input: string) => T;
|
||||
boolean?: (input: boolean) => T;
|
||||
number?: (input: number) => T;
|
||||
expression?: (input: JSExpression) => 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 = (
|
||||
ctx: INodeGeneratorContext,
|
||||
nodeItem: NodeSchema,
|
||||
handlers: CustomHandlerSet,
|
||||
) => CodePiece[];
|
||||
export type ExtGeneratorPlugin = (ctx: INodeGeneratorContext, nodeItem: NodeSchema) => CodePiece[];
|
||||
|
||||
export interface INodeGeneratorConfig {
|
||||
nodeTypeMapping?: Record<string, string>;
|
||||
}
|
||||
export type NodeGeneratorConfig = {
|
||||
handlers?: HandlerSet<string>;
|
||||
plugins?: ExtGeneratorPlugin[];
|
||||
};
|
||||
|
||||
export type NodeGenerator = (nodeItem: NodeData) => string;
|
||||
export type NodeGenerator = (nodeItem: NodeDataType) => string;
|
||||
|
||||
export interface INodeGeneratorContext {
|
||||
handlers: HandlerSet<string>;
|
||||
generator: NodeGenerator;
|
||||
}
|
||||
|
||||
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 = {
|
||||
handlers?: CompositeValueCustomHandlerSet;
|
||||
handlers?: HandlerSet<string>;
|
||||
containerHandler?: (value: string, isString: boolean, valStr: string) => string;
|
||||
nodeGenerator?: NodeGenerator;
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ import { generateJsSlot } from './jsSlot';
|
||||
import { isValidIdentifier } from './validate';
|
||||
import { camelize } from './common';
|
||||
|
||||
import { CompositeValueGeneratorOptions, CompositeTypeContainerHandler, CodeGeneratorError } from '../types';
|
||||
import { CompositeValueGeneratorOptions, CodeGeneratorError } from '../types';
|
||||
|
||||
function generateArray(value: CompositeArray, options: CompositeValueGeneratorOptions = {}): string {
|
||||
const body = value.map((v) => generateUnknownType(v, options)).join(',');
|
||||
@ -110,12 +110,12 @@ function generateUnknownType(value: CompositeValue, options: CompositeValueGener
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
const defaultContainer: CompositeTypeContainerHandler = (v: string) => v;
|
||||
const defaultContainer = (v: string) => v;
|
||||
|
||||
export function generateCompositeType(value: CompositeValue, options: CompositeValueGeneratorOptions = {}): string {
|
||||
const isStringType = _.isString(value);
|
||||
const result = generateUnknownType(value, options);
|
||||
const handler: CompositeTypeContainerHandler = options.containerHandler || defaultContainer;
|
||||
const handler = options.containerHandler || defaultContainer;
|
||||
|
||||
if (isStringType && result.length >= 2) {
|
||||
return handler(result, true, result.substring(1, result.length - 1));
|
||||
|
||||
@ -4,10 +4,12 @@ import {
|
||||
JSExpression,
|
||||
NodeData,
|
||||
NodeSchema,
|
||||
isNodeSchema,
|
||||
PropsMap,
|
||||
isJSExpression,
|
||||
isJSSlot,
|
||||
isDOMText,
|
||||
NodeDataType,
|
||||
} from '@ali/lowcode-types';
|
||||
|
||||
import {
|
||||
@ -18,11 +20,10 @@ import {
|
||||
NodeGenerator,
|
||||
ExtGeneratorPlugin,
|
||||
INodeGeneratorContext,
|
||||
INodeGeneratorConfig,
|
||||
NodeGeneratorConfig,
|
||||
} from '../types';
|
||||
|
||||
import { generateCompositeType } from './compositeType';
|
||||
import { generateExpression } from './jsExpression';
|
||||
|
||||
// tslint:disable-next-line: no-empty
|
||||
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[] {
|
||||
if (attrName === 'initValue') {
|
||||
return [];
|
||||
@ -149,102 +116,24 @@ export function generateAttrs(ctx: INodeGeneratorContext, nodeItem: NodeSchema):
|
||||
return pieces;
|
||||
}
|
||||
|
||||
function mapNodeName(src: string, mapping: Record<string, string>): string {
|
||||
if (mapping[src]) {
|
||||
return mapping[src];
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
export function generateBasicNode(
|
||||
ctx: INodeGeneratorContext,
|
||||
nodeItem: NodeSchema,
|
||||
mapping: Record<string, string>,
|
||||
): CodePiece[] {
|
||||
export function generateBasicNode(ctx: INodeGeneratorContext, nodeItem: NodeSchema): CodePiece[] {
|
||||
const pieces: CodePiece[] = [];
|
||||
const tagName = (ctx.handlers.tagName || _.identity)(nodeItem.componentName);
|
||||
|
||||
pieces.push({
|
||||
value: mapNodeName(nodeItem.componentName, mapping),
|
||||
value: tagName || '', // FIXME: type detection error
|
||||
type: PIECE_TYPE.TAG,
|
||||
});
|
||||
|
||||
return 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 = (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 {
|
||||
export function linkPieces(pieces: CodePiece[]): string {
|
||||
const tagsPieces = pieces.filter((p) => p.type === PIECE_TYPE.TAG);
|
||||
if (tagsPieces.length !== 1) {
|
||||
throw new CodeGeneratorError('One node only need one tag define');
|
||||
}
|
||||
|
||||
const tagName = (handlers.tagName || _.identity)(tagsPieces[0].value);
|
||||
const tagName = tagsPieces[0].value;
|
||||
|
||||
const beforeParts = pieces
|
||||
.filter((p) => p.type === PIECE_TYPE.BEFORE)
|
||||
@ -275,53 +164,133 @@ export function linkPieces(pieces: CodePiece[], handlers: CustomHandlerSet): str
|
||||
return `${beforeParts}<${tagName}${attrsParts} />${afterParts}`;
|
||||
}
|
||||
|
||||
export function createNodeGenerator(
|
||||
handlers: HandlerSet<string>,
|
||||
plugins: ExtGeneratorPlugin[],
|
||||
cfg?: INodeGeneratorConfig,
|
||||
): NodeGenerator {
|
||||
let nodeTypeMapping: Record<string, string> = {};
|
||||
if (cfg && cfg.nodeTypeMapping) {
|
||||
nodeTypeMapping = cfg.nodeTypeMapping;
|
||||
export function generateNodeSchema(nodeItem: NodeSchema, ctx: INodeGeneratorContext): string {
|
||||
let pieces: CodePiece[] = [];
|
||||
|
||||
plugins.forEach((p) => {
|
||||
pieces = pieces.concat(p(ctx, nodeItem));
|
||||
});
|
||||
pieces = pieces.concat(generateBasicNode(ctx, nodeItem));
|
||||
pieces = pieces.concat(generateAttrs(ctx, nodeItem));
|
||||
if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) {
|
||||
pieces = pieces.concat(
|
||||
handleChildren<string>(ctx, nodeItem.children, handlers).map((l) => ({
|
||||
type: PIECE_TYPE.CHILDREN,
|
||||
value: l,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const generateNode = (nodeItem: NodeSchema): string => {
|
||||
let pieces: CodePiece[] = [];
|
||||
const ctx: INodeGeneratorContext = {
|
||||
generator: generateNode,
|
||||
};
|
||||
return linkPieces(pieces);
|
||||
}
|
||||
|
||||
plugins.forEach((p) => {
|
||||
pieces = pieces.concat(p(ctx, nodeItem));
|
||||
// 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 = 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<string>(ctx, nodeItem.children, handlers).map((l) => ({
|
||||
type: PIECE_TYPE.CHILDREN,
|
||||
value: l,
|
||||
})),
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return linkPieces(pieces, customHandlers);
|
||||
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;
|
||||
}
|
||||
|
||||
export const generateString = (input: string) => [input];
|
||||
|
||||
export function createReactNodeGenerator(cfg?: INodeGeneratorConfig): NodeGenerator {
|
||||
return createNodeGenerator(
|
||||
{
|
||||
string: generateString,
|
||||
expression: (input) => [generateExpression(input)],
|
||||
},
|
||||
[generateReactCtrlLine],
|
||||
cfg,
|
||||
);
|
||||
// TODO: 需要一个 merge config 的方法。
|
||||
export function createReactNodeGenerator(cfg?: NodeGeneratorConfig): NodeGenerator {
|
||||
return createNodeGenerator({
|
||||
plugins: [generateReactCtrlLine],
|
||||
...cfg,
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user