refactor: 💡 friendly nodeToJsx API & esmodule alias logic

This commit is contained in:
春希 2020-08-17 17:07:02 +08:00
parent e689d7f341
commit 7359d1d985
11 changed files with 98 additions and 85 deletions

View File

@ -171,5 +171,5 @@ function exportProject() {
// main(); // main();
// exportModule(); // exportModule();
// exportProject(); exportProject();
demo(); // demo();

View File

@ -146,7 +146,7 @@ class SchemaParser implements ISchemaParser {
} }
} }
} }
return ['']; return '';
}, },
}, },
{ {
@ -236,7 +236,7 @@ class SchemaParser implements ISchemaParser {
return handleSubNodes<string>( return handleSubNodes<string>(
children, children,
{ {
node: (i: NodeSchema) => [i.componentName], node: (i: NodeSchema) => i.componentName,
}, },
{ {
rerun: true, rerun: true,

View File

@ -50,7 +50,12 @@ function groupDepsByPack(deps: IDependency[]): Record<string, IDependency[]> {
return depMap; 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[] = []; const chunks: ICodeChunk[] = [];
let defaultImport = ''; let defaultImport = '';
let defaultImportAs = ''; let defaultImportAs = '';
@ -60,7 +65,13 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st
const srcName = dep.exportName; const srcName = dep.exportName;
let targetName = dep.componentName || dep.exportName; let targetName = dep.componentName || dep.exportName;
// 如果是自组件,则导出父组件,并且根据自组件命名规则,判断是否需要定义标识符
if (dep.subName) { if (dep.subName) {
if (targetName !== `${srcName}.${dep.subName}`) {
if (!isValidIdentifier(targetName)) {
throw new CodeGeneratorError(`Invalid Identifier [${targetName}]`);
}
chunks.push({ chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: targetFileType, fileType: targetFileType,
@ -73,19 +84,6 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st
dependency: dep, dependency: dep,
}, },
}); });
if (targetName !== `${srcName}.${dep.subName}`) {
if (!isValidIdentifier(targetName)) {
throw new CodeGeneratorError(`Invalid Identifier [${targetName}]`);
}
chunks.push({
type: ChunkType.STRING,
fileType: targetFileType,
name: COMMON_CHUNK_NAME.FileVarDefine,
content: `const ${targetName} = ${srcName}.${dep.subName};`,
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport],
});
} }
targetName = srcName; targetName = srcName;
@ -94,30 +92,35 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st
if (dep.destructuring) { if (dep.destructuring) {
imports[srcName] = targetName; imports[srcName] = targetName;
} else if (defaultImport) { } else if (defaultImport) {
// 有些时候,可能已经从某个包里引入了一个东东,但是希望能再起个别名,这时候用赋值语句代替 throw new CodeGeneratorError(`[${pkg}] has more than one default export.`);
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,
},
});
} else { } else {
defaultImport = srcName; defaultImport = srcName;
defaultImportAs = targetName; 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']; const statementL = ['import'];
if (defaultImport) { if (defaultImport) {
statementL.push(defaultImportAs); statementL.push(useAliasName ? defaultImportAs : defaultImport);
if (items.length > 0) { if (items.length > 0) {
statementL.push(','); statementL.push(',');
} }
@ -152,13 +155,15 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st
} }
type PluginConfig = { type PluginConfig = {
fileType: string; fileType?: string; // 导出的文件类型
useAliasName?: boolean; // 是否使用 componentName 重命名组件 identifier
}; };
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?: PluginConfig) => { const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?: PluginConfig) => {
const cfg: PluginConfig = { const cfg = {
fileType: FileType.JS, fileType: FileType.JS,
...config, useAliasName: true,
...(config || {}),
}; };
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
@ -172,7 +177,7 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?: Plu
const packs = groupDepsByPack(ir.deps); const packs = groupDepsByPack(ir.deps);
Object.keys(packs).forEach((pkg) => { 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); next.chunks.push(...chunks);
}); });
} }

View File

@ -1,19 +1,11 @@
import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; // import { JSExpression } from '@ali/lowcode-types';
import { RAX_CHUNK_NAME } from './const';
import { getFuncExprBody, transformFuncExpr2MethodMember } from '../../../utils/jsExpression'; // import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
// import { RAX_CHUNK_NAME } from './const';
import { // import { getFuncExprBody, transformFuncExpr2MethodMember } from '../../../utils/jsExpression';
BuilderComponentPlugin,
BuilderComponentPluginFactory, import { BuilderComponentPlugin, BuilderComponentPluginFactory, ICodeStruct } from '../../../types';
ChunkType,
CodeGeneratorError,
FileType,
ICodeChunk,
ICodeStruct,
IContainerInfo,
JSExpression,
} from '../../../types';
type PluginConfig = { type PluginConfig = {
fileType: string; fileType: string;
@ -22,12 +14,12 @@ type PluginConfig = {
}; };
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => { const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
const cfg: PluginConfig = { // const cfg: PluginConfig = {
fileType: FileType.JSX, // fileType: FileType.JSX,
exportNameMapping: {}, // exportNameMapping: {},
normalizeNameMapping: {}, // normalizeNameMapping: {},
...config, // ...config,
}; // };
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {

View File

@ -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 { import {
BuilderComponentPlugin, BuilderComponentPlugin,

View File

@ -28,7 +28,7 @@ const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" /> <meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<title>${ir.meta.name}</title> <title>${ir?.meta?.name || 'Ice App'}</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -34,7 +34,7 @@ function Document() {
name="viewport" name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover" 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 /> <Style />
</head> </head>
<body> <body>

View File

@ -27,7 +27,7 @@ export default function createIceJsProjectBuilder(): IProjectBuilder {
plugins: { plugins: {
components: [ components: [
commonDeps(), commonDeps(),
esModule({ fileType: 'jsx' }), esModule({ fileType: 'jsx', useAliasName: false }),
containerClass(), containerClass(),
containerInitState(), containerInitState(),
containerMethods(), containerMethods(),

View File

@ -154,9 +154,12 @@ export interface HandlerSet<T> {
slot?: (input: JSSlot) => T; slot?: (input: JSSlot) => T;
node?: (input: NodeSchema) => T; node?: (input: NodeSchema) => T;
array?: (input: any[]) => T; array?: (input: any[]) => T;
children?: (input: T[]) => T;
object?: (input: object) => T; object?: (input: object) => T;
common?: (input: unknown) => T; common?: (input: unknown) => T;
tagName?: (input: string) => T; tagName?: (input: string) => T;
loopDataExpr?: (input: string) => T;
conditionExpr?: (input: string) => T;
} }
export type ExtGeneratorPlugin = (ctx: INodeGeneratorContext, nodeItem: NodeSchema) => CodePiece[]; export type ExtGeneratorPlugin = (ctx: INodeGeneratorContext, nodeItem: NodeSchema) => CodePiece[];
@ -170,6 +173,7 @@ export type NodeGenerator = (nodeItem: NodeDataType) => string;
export interface INodeGeneratorContext { export interface INodeGeneratorContext {
handlers: HandlerSet<string>; handlers: HandlerSet<string>;
plugins: ExtGeneratorPlugin[];
generator: NodeGenerator; generator: NodeGenerator;
} }

View File

@ -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'; import { CodeGeneratorError } from '../types';
export function transformFuncExpr2MethodMember(methodName: string, functionBody: string): string { export function transformFuncExpr2MethodMember(methodName: string, functionBody: string): string {
@ -40,11 +40,11 @@ export function getArrowFunction(functionBody: string) {
} }
export function isJsCode(value: unknown): boolean { export function isJsCode(value: unknown): boolean {
return isJsExpression(value) || isJsFunction(value); return isJSExpression(value) || isJSFunction(value);
} }
export function generateExpression(value: any): string { export function generateExpression(value: any): string {
if (isJsExpression(value)) { if (isJSExpression(value)) {
return (value as JSExpression).value || 'null'; return (value as JSExpression).value || 'null';
} }

View File

@ -18,7 +18,6 @@ import {
CodePiece, CodePiece,
HandlerSet, HandlerSet,
NodeGenerator, NodeGenerator,
ExtGeneratorPlugin,
INodeGeneratorContext, INodeGeneratorContext,
NodeGeneratorConfig, NodeGeneratorConfig,
} from '../types'; } from '../types';
@ -26,7 +25,7 @@ import {
import { generateCompositeType } from './compositeType'; import { generateCompositeType } from './compositeType';
// tslint:disable-next-line: no-empty // tslint:disable-next-line: no-empty
const noop = () => []; const noop = () => undefined;
const handleChildrenDefaultOptions = { const handleChildrenDefaultOptions = {
rerun: false, rerun: false,
@ -47,19 +46,24 @@ export function handleSubNodes<T>(
if (Array.isArray(children)) { if (Array.isArray(children)) {
const list: NodeData[] = children as NodeData[]; const list: NodeData[] = children as NodeData[];
return list.map((child) => handleSubNodes(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 (isDOMText(children)) { }
let result: T | undefined = undefined;
const childrenRes: T[] = [];
if (isDOMText(children)) {
const handler = handlers.string || handlers.common || noop; const handler = handlers.string || handlers.common || noop;
return handler(children as string); result = handler(children as string);
} else if (isJSExpression(children)) { } else if (isJSExpression(children)) {
const handler = handlers.expression || handlers.common || noop; const handler = handlers.expression || handlers.common || noop;
return handler(children as JSExpression); result = handler(children as JSExpression);
} else { } else {
const handler = handlers.node || handlers.common || noop; const handler = handlers.node || handlers.common || noop;
const child = children as NodeSchema; const child = children as NodeSchema;
let curRes = handler(child); result = handler(child);
if (opt.rerun && child.children) { if (opt.rerun && child.children) {
const childRes = handleSubNodes(child.children, handlers, opt); const childRes = handleSubNodes(child.children, handlers, opt);
curRes = curRes.concat(childRes || []); childrenRes.push(...childRes);
} }
if (child.props) { if (child.props) {
// FIXME: currently only support PropsMap // FIXME: currently only support PropsMap
@ -69,11 +73,16 @@ export function handleSubNodes<T>(
.forEach((propName) => { .forEach((propName) => {
const soltVals = (childProps[propName] as JSSlot).value; const soltVals = (childProps[propName] as JSSlot).value;
const childRes = handleSubNodes(soltVals, handlers, opt); 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[] { 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 { export function generateNodeSchema(nodeItem: NodeSchema, ctx: INodeGeneratorContext): string {
let pieces: CodePiece[] = []; let pieces: CodePiece[] = [];
plugins.forEach((p) => { ctx.plugins.forEach((p) => {
pieces = pieces.concat(p(ctx, nodeItem)); pieces = pieces.concat(p(ctx, nodeItem));
}); });
pieces = pieces.concat(generateBasicNode(ctx, nodeItem)); 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) {
pieces = pieces.concat( if (nodeItem.children) {
handleChildren<string>(ctx, nodeItem.children, handlers).map((l) => ({ const childrenStr = ctx.generator(nodeItem.children);
pieces.push({
type: PIECE_TYPE.CHILDREN, type: PIECE_TYPE.CHILDREN,
value: l, value: childrenStr,
})), });
);
} }
return linkPieces(pieces); return linkPieces(pieces);
@ -191,6 +201,7 @@ export function generateNodeSchema(nodeItem: NodeSchema, ctx: INodeGeneratorCont
/** /**
* JSX React loop condition * JSX React loop condition
* @type ExtGeneratorPlugin
* *
* @export * @export
* @param {NodeSchema} nodeItem UI * @param {NodeSchema} nodeItem UI
@ -251,15 +262,15 @@ export function generateReactCtrlLine(ctx: INodeGeneratorContext, nodeItem: Node
return pieces; return pieces;
} }
const handleArray = (v: string[]) => v.join(''); const handleChildren = (v: string[]) => v.join('');
export function createNodeGenerator(cfg: NodeGeneratorConfig = {}): NodeGenerator { export function createNodeGenerator(cfg: NodeGeneratorConfig = {}): NodeGenerator {
let ctx: INodeGeneratorContext = { handlers: {}, generator: () => '' }; let ctx: INodeGeneratorContext = { handlers: {}, plugins: [], generator: () => '' };
const generateNode = (nodeItem: NodeDataType): string => { const generateNode = (nodeItem: NodeDataType): string => {
if (_.isArray(nodeItem)) { if (_.isArray(nodeItem)) {
const resList = nodeItem.map((n) => generateNode(n)); const resList = nodeItem.map((n) => generateNode(n));
return (cfg?.handlers?.array || handleArray)(resList); return (cfg?.handlers?.children || handleChildren)(resList);
} }
if (isNodeSchema(nodeItem)) { if (isNodeSchema(nodeItem)) {
@ -281,6 +292,7 @@ export function createNodeGenerator(cfg: NodeGeneratorConfig = {}): NodeGenerato
ctx = { ctx = {
handlers: cfg?.handlers || {}, handlers: cfg?.handlers || {},
plugins: cfg.plugins || [],
generator: generateNode, generator: generateNode,
}; };