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 { 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);

View File

@ -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 = {

View File

@ -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 = {

View File

@ -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;
};

View File

@ -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));

View File

@ -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,
});
}