mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-01 05:30:40 +00:00
refactor: 💡 friendly nodeToJsx API & esmodule alias logic
This commit is contained in:
parent
e689d7f341
commit
7359d1d985
@ -171,5 +171,5 @@ function exportProject() {
|
||||
|
||||
// main();
|
||||
// exportModule();
|
||||
// exportProject();
|
||||
demo();
|
||||
exportProject();
|
||||
// demo();
|
||||
|
||||
@ -146,7 +146,7 @@ class SchemaParser implements ISchemaParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
return [''];
|
||||
return '';
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -236,7 +236,7 @@ class SchemaParser implements ISchemaParser {
|
||||
return handleSubNodes<string>(
|
||||
children,
|
||||
{
|
||||
node: (i: NodeSchema) => [i.componentName],
|
||||
node: (i: NodeSchema) => i.componentName,
|
||||
},
|
||||
{
|
||||
rerun: true,
|
||||
|
||||
@ -50,7 +50,12 @@ function groupDepsByPack(deps: IDependency[]): Record<string, IDependency[]> {
|
||||
return depMap;
|
||||
}
|
||||
|
||||
function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: string): ICodeChunk[] {
|
||||
function buildPackageImport(
|
||||
pkg: string,
|
||||
deps: IDependency[],
|
||||
targetFileType: string,
|
||||
useAliasName: boolean,
|
||||
): ICodeChunk[] {
|
||||
const chunks: ICodeChunk[] = [];
|
||||
let defaultImport = '';
|
||||
let defaultImportAs = '';
|
||||
@ -60,20 +65,8 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st
|
||||
const srcName = dep.exportName;
|
||||
let targetName = dep.componentName || dep.exportName;
|
||||
|
||||
// 如果是自组件,则导出父组件,并且根据自组件命名规则,判断是否需要定义标识符
|
||||
if (dep.subName) {
|
||||
chunks.push({
|
||||
type: ChunkType.STRING,
|
||||
fileType: targetFileType,
|
||||
name: COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||
content: `const ${targetName} = ${srcName}.${dep.subName};`,
|
||||
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport],
|
||||
ext: {
|
||||
originalName: `${srcName}.${dep.subName}`,
|
||||
aliasName: targetName,
|
||||
dependency: dep,
|
||||
},
|
||||
});
|
||||
|
||||
if (targetName !== `${srcName}.${dep.subName}`) {
|
||||
if (!isValidIdentifier(targetName)) {
|
||||
throw new CodeGeneratorError(`Invalid Identifier [${targetName}]`);
|
||||
@ -82,9 +75,14 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st
|
||||
chunks.push({
|
||||
type: ChunkType.STRING,
|
||||
fileType: targetFileType,
|
||||
name: COMMON_CHUNK_NAME.FileVarDefine,
|
||||
name: COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||
content: `const ${targetName} = ${srcName}.${dep.subName};`,
|
||||
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport],
|
||||
ext: {
|
||||
originalName: `${srcName}.${dep.subName}`,
|
||||
aliasName: targetName,
|
||||
dependency: dep,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -94,30 +92,35 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st
|
||||
if (dep.destructuring) {
|
||||
imports[srcName] = targetName;
|
||||
} else if (defaultImport) {
|
||||
// 有些时候,可能已经从某个包里引入了一个东东,但是希望能再起个别名,这时候用赋值语句代替
|
||||
chunks.push({
|
||||
type: ChunkType.STRING,
|
||||
fileType: targetFileType,
|
||||
name: COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||
content: `const ${targetName} = ${defaultImportAs};`,
|
||||
linkAfter: [COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||
ext: {
|
||||
originalName: defaultImportAs,
|
||||
aliasName: targetName,
|
||||
dependency: dep,
|
||||
},
|
||||
});
|
||||
throw new CodeGeneratorError(`[${pkg}] has more than one default export.`);
|
||||
} else {
|
||||
defaultImport = srcName;
|
||||
defaultImportAs = targetName;
|
||||
}
|
||||
|
||||
if (targetName !== srcName) {
|
||||
chunks.push({
|
||||
type: ChunkType.STRING,
|
||||
fileType: targetFileType,
|
||||
name: COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||
content: '',
|
||||
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport],
|
||||
ext: {
|
||||
originalName: srcName,
|
||||
aliasName: targetName,
|
||||
dependency: dep,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const items = Object.keys(imports).map((src) => (src === imports[src] ? src : `${src} as ${imports[src]}`));
|
||||
const items = Object.keys(imports).map((src) =>
|
||||
src === imports[src] || !useAliasName ? src : `${src} as ${imports[src]}`,
|
||||
);
|
||||
|
||||
const statementL = ['import'];
|
||||
if (defaultImport) {
|
||||
statementL.push(defaultImportAs);
|
||||
statementL.push(useAliasName ? defaultImportAs : defaultImport);
|
||||
if (items.length > 0) {
|
||||
statementL.push(',');
|
||||
}
|
||||
@ -152,13 +155,15 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st
|
||||
}
|
||||
|
||||
type PluginConfig = {
|
||||
fileType: string;
|
||||
fileType?: string; // 导出的文件类型
|
||||
useAliasName?: boolean; // 是否使用 componentName 重命名组件 identifier
|
||||
};
|
||||
|
||||
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?: PluginConfig) => {
|
||||
const cfg: PluginConfig = {
|
||||
const cfg = {
|
||||
fileType: FileType.JS,
|
||||
...config,
|
||||
useAliasName: true,
|
||||
...(config || {}),
|
||||
};
|
||||
|
||||
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||
@ -172,7 +177,7 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?: Plu
|
||||
const packs = groupDepsByPack(ir.deps);
|
||||
|
||||
Object.keys(packs).forEach((pkg) => {
|
||||
const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType);
|
||||
const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType, cfg.useAliasName);
|
||||
next.chunks.push(...chunks);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,19 +1,11 @@
|
||||
import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
|
||||
import { RAX_CHUNK_NAME } from './const';
|
||||
// import { JSExpression } from '@ali/lowcode-types';
|
||||
|
||||
import { getFuncExprBody, transformFuncExpr2MethodMember } from '../../../utils/jsExpression';
|
||||
// import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
|
||||
// import { RAX_CHUNK_NAME } from './const';
|
||||
|
||||
import {
|
||||
BuilderComponentPlugin,
|
||||
BuilderComponentPluginFactory,
|
||||
ChunkType,
|
||||
CodeGeneratorError,
|
||||
FileType,
|
||||
ICodeChunk,
|
||||
ICodeStruct,
|
||||
IContainerInfo,
|
||||
JSExpression,
|
||||
} from '../../../types';
|
||||
// import { getFuncExprBody, transformFuncExpr2MethodMember } from '../../../utils/jsExpression';
|
||||
|
||||
import { BuilderComponentPlugin, BuilderComponentPluginFactory, ICodeStruct } from '../../../types';
|
||||
|
||||
type PluginConfig = {
|
||||
fileType: string;
|
||||
@ -22,12 +14,12 @@ type PluginConfig = {
|
||||
};
|
||||
|
||||
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||
const cfg: PluginConfig = {
|
||||
fileType: FileType.JSX,
|
||||
exportNameMapping: {},
|
||||
normalizeNameMapping: {},
|
||||
...config,
|
||||
};
|
||||
// const cfg: PluginConfig = {
|
||||
// fileType: FileType.JSX,
|
||||
// exportNameMapping: {},
|
||||
// normalizeNameMapping: {},
|
||||
// ...config,
|
||||
// };
|
||||
|
||||
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||
const next: ICodeStruct = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
|
||||
import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator';
|
||||
|
||||
import {
|
||||
BuilderComponentPlugin,
|
||||
|
||||
@ -28,7 +28,7 @@ const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>${ir.meta.name}</title>
|
||||
<title>${ir?.meta?.name || 'Ice App'}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@ -34,7 +34,7 @@ function Document() {
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover"
|
||||
/>
|
||||
<title>${ir.meta.name}</title>
|
||||
<title>${ir?.meta?.name || 'Rax App'}</title>
|
||||
<Style />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -27,7 +27,7 @@ export default function createIceJsProjectBuilder(): IProjectBuilder {
|
||||
plugins: {
|
||||
components: [
|
||||
commonDeps(),
|
||||
esModule({ fileType: 'jsx' }),
|
||||
esModule({ fileType: 'jsx', useAliasName: false }),
|
||||
containerClass(),
|
||||
containerInitState(),
|
||||
containerMethods(),
|
||||
|
||||
@ -154,9 +154,12 @@ export interface HandlerSet<T> {
|
||||
slot?: (input: JSSlot) => T;
|
||||
node?: (input: NodeSchema) => T;
|
||||
array?: (input: any[]) => T;
|
||||
children?: (input: T[]) => T;
|
||||
object?: (input: object) => T;
|
||||
common?: (input: unknown) => T;
|
||||
tagName?: (input: string) => T;
|
||||
loopDataExpr?: (input: string) => T;
|
||||
conditionExpr?: (input: string) => T;
|
||||
}
|
||||
|
||||
export type ExtGeneratorPlugin = (ctx: INodeGeneratorContext, nodeItem: NodeSchema) => CodePiece[];
|
||||
@ -170,6 +173,7 @@ export type NodeGenerator = (nodeItem: NodeDataType) => string;
|
||||
|
||||
export interface INodeGeneratorContext {
|
||||
handlers: HandlerSet<string>;
|
||||
plugins: ExtGeneratorPlugin[];
|
||||
generator: NodeGenerator;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { JSExpression, JSFunction, isJsExpression, isJsFunction } from '@ali/lowcode-types';
|
||||
import { JSExpression, JSFunction, isJSExpression, isJSFunction } from '@ali/lowcode-types';
|
||||
import { CodeGeneratorError } from '../types';
|
||||
|
||||
export function transformFuncExpr2MethodMember(methodName: string, functionBody: string): string {
|
||||
@ -40,11 +40,11 @@ export function getArrowFunction(functionBody: string) {
|
||||
}
|
||||
|
||||
export function isJsCode(value: unknown): boolean {
|
||||
return isJsExpression(value) || isJsFunction(value);
|
||||
return isJSExpression(value) || isJSFunction(value);
|
||||
}
|
||||
|
||||
export function generateExpression(value: any): string {
|
||||
if (isJsExpression(value)) {
|
||||
if (isJSExpression(value)) {
|
||||
return (value as JSExpression).value || 'null';
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,6 @@ import {
|
||||
CodePiece,
|
||||
HandlerSet,
|
||||
NodeGenerator,
|
||||
ExtGeneratorPlugin,
|
||||
INodeGeneratorContext,
|
||||
NodeGeneratorConfig,
|
||||
} from '../types';
|
||||
@ -26,7 +25,7 @@ import {
|
||||
import { generateCompositeType } from './compositeType';
|
||||
|
||||
// tslint:disable-next-line: no-empty
|
||||
const noop = () => [];
|
||||
const noop = () => undefined;
|
||||
|
||||
const handleChildrenDefaultOptions = {
|
||||
rerun: false,
|
||||
@ -47,19 +46,24 @@ export function handleSubNodes<T>(
|
||||
if (Array.isArray(children)) {
|
||||
const list: NodeData[] = children as NodeData[];
|
||||
return list.map((child) => handleSubNodes(child, handlers, opt)).reduce((p, c) => p.concat(c), []);
|
||||
} else if (isDOMText(children)) {
|
||||
}
|
||||
|
||||
let result: T | undefined = undefined;
|
||||
const childrenRes: T[] = [];
|
||||
if (isDOMText(children)) {
|
||||
const handler = handlers.string || handlers.common || noop;
|
||||
return handler(children as string);
|
||||
result = handler(children as string);
|
||||
} else if (isJSExpression(children)) {
|
||||
const handler = handlers.expression || handlers.common || noop;
|
||||
return handler(children as JSExpression);
|
||||
result = handler(children as JSExpression);
|
||||
} else {
|
||||
const handler = handlers.node || handlers.common || noop;
|
||||
const child = children as NodeSchema;
|
||||
let curRes = handler(child);
|
||||
result = handler(child);
|
||||
|
||||
if (opt.rerun && child.children) {
|
||||
const childRes = handleSubNodes(child.children, handlers, opt);
|
||||
curRes = curRes.concat(childRes || []);
|
||||
childrenRes.push(...childRes);
|
||||
}
|
||||
if (child.props) {
|
||||
// FIXME: currently only support PropsMap
|
||||
@ -69,11 +73,16 @@ export function handleSubNodes<T>(
|
||||
.forEach((propName) => {
|
||||
const soltVals = (childProps[propName] as JSSlot).value;
|
||||
const childRes = handleSubNodes(soltVals, handlers, opt);
|
||||
curRes = curRes.concat(childRes || []);
|
||||
childrenRes.push(...childRes);
|
||||
});
|
||||
}
|
||||
return curRes;
|
||||
}
|
||||
|
||||
if (result !== undefined) {
|
||||
childrenRes.unshift(result);
|
||||
}
|
||||
|
||||
return childrenRes;
|
||||
}
|
||||
|
||||
export function generateAttr(ctx: INodeGeneratorContext, attrName: string, attrValue: any): CodePiece[] {
|
||||
@ -167,18 +176,19 @@ export function linkPieces(pieces: CodePiece[]): string {
|
||||
export function generateNodeSchema(nodeItem: NodeSchema, ctx: INodeGeneratorContext): string {
|
||||
let pieces: CodePiece[] = [];
|
||||
|
||||
plugins.forEach((p) => {
|
||||
ctx.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,
|
||||
})),
|
||||
);
|
||||
|
||||
if (nodeItem.children) {
|
||||
const childrenStr = ctx.generator(nodeItem.children);
|
||||
|
||||
pieces.push({
|
||||
type: PIECE_TYPE.CHILDREN,
|
||||
value: childrenStr,
|
||||
});
|
||||
}
|
||||
|
||||
return linkPieces(pieces);
|
||||
@ -191,6 +201,7 @@ export function generateNodeSchema(nodeItem: NodeSchema, ctx: INodeGeneratorCont
|
||||
|
||||
/**
|
||||
* JSX 生成逻辑插件。在 React 代码模式下生成 loop 与 condition 相关的逻辑代码
|
||||
* @type ExtGeneratorPlugin
|
||||
*
|
||||
* @export
|
||||
* @param {NodeSchema} nodeItem 当前 UI 节点
|
||||
@ -251,15 +262,15 @@ export function generateReactCtrlLine(ctx: INodeGeneratorContext, nodeItem: Node
|
||||
return pieces;
|
||||
}
|
||||
|
||||
const handleArray = (v: string[]) => v.join('');
|
||||
const handleChildren = (v: string[]) => v.join('');
|
||||
|
||||
export function createNodeGenerator(cfg: NodeGeneratorConfig = {}): NodeGenerator {
|
||||
let ctx: INodeGeneratorContext = { handlers: {}, generator: () => '' };
|
||||
let ctx: INodeGeneratorContext = { handlers: {}, plugins: [], generator: () => '' };
|
||||
|
||||
const generateNode = (nodeItem: NodeDataType): string => {
|
||||
if (_.isArray(nodeItem)) {
|
||||
const resList = nodeItem.map((n) => generateNode(n));
|
||||
return (cfg?.handlers?.array || handleArray)(resList);
|
||||
return (cfg?.handlers?.children || handleChildren)(resList);
|
||||
}
|
||||
|
||||
if (isNodeSchema(nodeItem)) {
|
||||
@ -281,6 +292,7 @@ export function createNodeGenerator(cfg: NodeGeneratorConfig = {}): NodeGenerato
|
||||
|
||||
ctx = {
|
||||
handlers: cfg?.handlers || {},
|
||||
plugins: cfg.plugins || [],
|
||||
generator: generateNode,
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user