diff --git a/packages/code-generator/src/const/generator.ts b/packages/code-generator/src/const/generator.ts index 6f471e2a4..11fe67b81 100644 --- a/packages/code-generator/src/const/generator.ts +++ b/packages/code-generator/src/const/generator.ts @@ -1,6 +1,7 @@ export const COMMON_CHUNK_NAME = { ExternalDepsImport: 'CommonExternalDependencyImport', InternalDepsImport: 'CommonInternalDependencyImport', + ImportAliasDefine: 'CommonImportAliasDefine', FileVarDefine: 'CommonFileScopeVarDefine', FileUtilDefine: 'CommonFileScopeMethodDefine', FileMainContent: 'CommonFileMainContent', @@ -28,18 +29,22 @@ export const CLASS_DEFINE_CHUNK_NAME = { export const DEFAULT_LINK_AFTER = { [COMMON_CHUNK_NAME.ExternalDepsImport]: [], [COMMON_CHUNK_NAME.InternalDepsImport]: [COMMON_CHUNK_NAME.ExternalDepsImport], + [COMMON_CHUNK_NAME.ImportAliasDefine]: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], [COMMON_CHUNK_NAME.FileVarDefine]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, ], [COMMON_CHUNK_NAME.FileUtilDefine]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, ], [CLASS_DEFINE_CHUNK_NAME.Start]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], @@ -50,20 +55,13 @@ export const DEFAULT_LINK_AFTER = { CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsVarMethod, ], - [CLASS_DEFINE_CHUNK_NAME.ConstructorContent]: [ - CLASS_DEFINE_CHUNK_NAME.ConstructorStart, - ], + [CLASS_DEFINE_CHUNK_NAME.ConstructorContent]: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], [CLASS_DEFINE_CHUNK_NAME.ConstructorEnd]: [ CLASS_DEFINE_CHUNK_NAME.ConstructorStart, CLASS_DEFINE_CHUNK_NAME.ConstructorContent, ], - [CLASS_DEFINE_CHUNK_NAME.StaticVar]: [ - CLASS_DEFINE_CHUNK_NAME.Start, - ], - [CLASS_DEFINE_CHUNK_NAME.StaticMethod]: [ - CLASS_DEFINE_CHUNK_NAME.Start, - CLASS_DEFINE_CHUNK_NAME.StaticVar, - ], + [CLASS_DEFINE_CHUNK_NAME.StaticVar]: [CLASS_DEFINE_CHUNK_NAME.Start], + [CLASS_DEFINE_CHUNK_NAME.StaticMethod]: [CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar], [CLASS_DEFINE_CHUNK_NAME.InsVar]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, @@ -105,6 +103,7 @@ export const DEFAULT_LINK_AFTER = { [COMMON_CHUNK_NAME.FileMainContent]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, CLASS_DEFINE_CHUNK_NAME.End, @@ -112,6 +111,7 @@ export const DEFAULT_LINK_AFTER = { [COMMON_CHUNK_NAME.FileExport]: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, CLASS_DEFINE_CHUNK_NAME.End, @@ -120,6 +120,6 @@ export const DEFAULT_LINK_AFTER = { [COMMON_CHUNK_NAME.StyleDepsImport]: [], [COMMON_CHUNK_NAME.StyleCssContent]: [COMMON_CHUNK_NAME.StyleDepsImport], [COMMON_CHUNK_NAME.HtmlContent]: [], -} +}; export const COMMON_SUB_MODULE_NAME = 'index'; diff --git a/packages/code-generator/src/parser/SchemaParser.ts b/packages/code-generator/src/parser/SchemaParser.ts index 5424bbc5a..0dd66af6d 100644 --- a/packages/code-generator/src/parser/SchemaParser.ts +++ b/packages/code-generator/src/parser/SchemaParser.ts @@ -39,9 +39,7 @@ const defaultContainer: IContainerInfo = { class SchemaParser implements ISchemaParser { public validate(schema: IBasicSchema): boolean { if (SUPPORT_SCHEMA_VERSION_LIST.indexOf(schema.version) < 0) { - throw new CompatibilityError( - `Not support schema with version [${schema.version}]`, - ); + throw new CompatibilityError(`Not support schema with version [${schema.version}]`); } return true; @@ -65,19 +63,25 @@ class SchemaParser implements ISchemaParser { } // 解析三方组件依赖 - schema.componentsMap.forEach(info => { - info.dependencyType = DependencyType.External; - info.importName = info.componentName; - compDeps[info.componentName] = info; + schema.componentsMap.forEach((info) => { + if (info.componentName) { + compDeps[info.componentName] = { + ...info, + dependencyType: DependencyType.External, + importName: info.componentName, + exportName: info.exportName ?? info.componentName, + version: info.version || '*', + destructuring: info.destructuring ?? false, + }; + } }); let containers: IContainerInfo[]; // Test if this is a lowcode component without container if (schema.componentsTree.length > 0) { - const firstRoot: IContainerNodeItem = schema - .componentsTree[0] as IContainerNodeItem; + const firstRoot: IContainerNodeItem = schema.componentsTree[0] as IContainerNodeItem; - if (!firstRoot.fileName) { + if (!('fileName' in firstRoot) || !firstRoot.fileName) { // 整个 schema 描述一个容器,且无根节点定义 const container: IContainerInfo = { ...defaultContainer, @@ -86,8 +90,8 @@ class SchemaParser implements ISchemaParser { containers = [container]; } else { // 普通带 1 到多个容器的 schema - containers = schema.componentsTree.map(n => { - const subRoot = n as IContainerNodeItem; + containers = schema.componentsTree.map((n) => { + const subRoot = n; const container: IContainerInfo = { ...subRoot, containerType: subRoot.componentName, @@ -101,7 +105,7 @@ class SchemaParser implements ISchemaParser { } // 建立所有容器的内部依赖索引 - containers.forEach(container => { + containers.forEach((container) => { let type; switch (container.containerType) { case 'Page': @@ -127,31 +131,31 @@ class SchemaParser implements ISchemaParser { }); // 分析容器内部组件依赖 - containers.forEach(container => { + containers.forEach((container) => { if (container.children) { // const depNames = this.getComponentNames(container.children); // container.deps = uniqueArray(depNames) // .map(depName => internalDeps[depName] || compDeps[depName]) // .filter(dep => !!dep); - container.deps = Object.keys(compDeps).map( - depName => compDeps[depName], - ); + container.deps = Object.keys(compDeps).map((depName) => compDeps[depName]); } }); - const containersDeps = ([] as IDependency[]).concat(...containers.map(c => c.deps || [])); + const containersDeps = ([] as IDependency[]).concat(...containers.map((c) => c.deps || [])); // 分析路由配置 + // TODO: 低代码规范里面的路由是咋弄的? const routes = containers - .filter(container => container.containerType === 'Page') - .map(page => { - const meta = page.meta as IPageMeta; + .filter((container) => container.containerType === 'Page') + .map((page) => { + const meta = (page as { meta?: IPageMeta }).meta as IPageMeta; if (meta) { return { path: meta.router, componentName: page.moduleName, }; } + return { path: '', componentName: page.moduleName, @@ -159,16 +163,14 @@ class SchemaParser implements ISchemaParser { }); const routerDeps = routes - .map(r => internalDeps[r.componentName] || compDeps[r.componentName]) - .filter(dep => !!dep); + .map((r) => internalDeps[r.componentName] || compDeps[r.componentName]) + .filter((dep) => !!dep); // 分析 Utils 依赖 let utils: IUtilItem[]; if (schema.utils) { utils = schema.utils; - utilsDeps = schema.utils - .filter(u => u.type !== 'function') - .map(u => u.content as IExternalDependency); + utilsDeps = schema.utils.filter((u) => u.type !== 'function').map((u) => u.content as IExternalDependency); } else { utils = []; } @@ -197,7 +199,7 @@ class SchemaParser implements ISchemaParser { } public getComponentNames(children: ChildNodeType): string[] { - return handleChildren(children, { + return handleChildren(children, { node: (i: IComponentNodeItem) => [i.componentName], }); } diff --git a/packages/code-generator/src/plugins/common/esmodule.ts b/packages/code-generator/src/plugins/common/esmodule.ts index 316ed5821..3d615ed9e 100644 --- a/packages/code-generator/src/plugins/common/esmodule.ts +++ b/packages/code-generator/src/plugins/common/esmodule.ts @@ -25,12 +25,9 @@ function groupDepsByPack(deps: IDependency[]): Record { depMap[pkg].push(dep); }; - deps.forEach(dep => { + deps.forEach((dep) => { if (dep.dependencyType === DependencyType.Internal) { - addDep( - `${(dep as IInternalDependency).moduleName}${dep.main || ''}`, - dep, - ); + addDep(`${(dep as IInternalDependency).moduleName}${dep.main || ''}`, dep); } else { addDep(`${(dep as IExternalDependency).package}${dep.main || ''}`, dep); } @@ -39,17 +36,13 @@ function groupDepsByPack(deps: IDependency[]): Record { return depMap; } -function buildPackageImport( - pkg: string, - deps: IDependency[], - targetFileType: string, -): ICodeChunk[] { +function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: string): ICodeChunk[] { const chunks: ICodeChunk[] = []; let defaultImport: string = ''; let defaultImportAs: string = ''; const imports: Record = {}; - deps.forEach(dep => { + deps.forEach((dep) => { const srcName = dep.exportName; let targetName = dep.importName || dep.exportName; if (dep.subName) { @@ -60,12 +53,14 @@ function buildPackageImport( 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, - ], + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], + ext: { + originalName: `${srcName}.${dep.subName}`, + aliasName: targetName, + dependency: dep, + }, }); targetName = srcName; @@ -74,18 +69,26 @@ function buildPackageImport( if (dep.destructuring) { imports[srcName] = targetName; } 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 { defaultImport = srcName; defaultImportAs = targetName; } }); - 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] ? src : `${src} as ${imports[src]}`)); const statementL = ['import']; if (defaultImport) { @@ -125,7 +128,7 @@ function buildPackageImport( type PluginConfig = { fileType: string; -} +}; const pluginFactory: BuilderComponentPluginFactory = (config?: PluginConfig) => { const cfg: PluginConfig = { @@ -143,9 +146,9 @@ const pluginFactory: BuilderComponentPluginFactory = (config?: Plu if (ir && ir.deps && ir.deps.length > 0) { const packs = groupDepsByPack(ir.deps); - Object.keys(packs).forEach(pkg => { + Object.keys(packs).forEach((pkg) => { const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType); - next.chunks.push.apply(next.chunks, chunks); + next.chunks.push(...chunks); }); } diff --git a/packages/code-generator/src/plugins/component/rax/const.ts b/packages/code-generator/src/plugins/component/rax/const.ts index 6a13c92a6..d604b27e9 100644 --- a/packages/code-generator/src/plugins/component/rax/const.ts +++ b/packages/code-generator/src/plugins/component/rax/const.ts @@ -1,9 +1,12 @@ export const RAX_CHUNK_NAME = { - ClassDidMountStart: 'RaxComponentClassDidMountStart', + ClassDidMountBegin: 'RaxComponentClassDidMountBegin', ClassDidMountContent: 'RaxComponentClassDidMountContent', ClassDidMountEnd: 'RaxComponentClassDidMountEnd', - ClassRenderStart: 'RaxComponentClassRenderStart', + ClassRenderBegin: 'RaxComponentClassRenderBegin', ClassRenderPre: 'RaxComponentClassRenderPre', ClassRenderJSX: 'RaxComponentClassRenderJSX', ClassRenderEnd: 'RaxComponentClassRenderEnd', + MethodsBegin: 'RaxComponentMethodsBegin', + MethodsContent: 'RaxComponentMethodsContent', + MethodsEnd: 'RaxComponentMethodsEnd', }; diff --git a/packages/code-generator/src/plugins/component/rax/containerClass.ts b/packages/code-generator/src/plugins/component/rax/containerClass.ts index a38d920d4..4ec4b6245 100644 --- a/packages/code-generator/src/plugins/component/rax/containerClass.ts +++ b/packages/code-generator/src/plugins/component/rax/containerClass.ts @@ -30,12 +30,12 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], }); - next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, @@ -43,8 +43,9 @@ const pluginFactory: BuilderComponentPluginFactory = () => { content: `}`, linkAfter: [ CLASS_DEFINE_CHUNK_NAME.Start, - RAX_CHUNK_NAME.ClassRenderEnd, CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + RAX_CHUNK_NAME.ClassRenderEnd, + RAX_CHUNK_NAME.MethodsEnd, ], }); @@ -70,13 +71,9 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, - name: RAX_CHUNK_NAME.ClassDidMountStart, + name: RAX_CHUNK_NAME.ClassDidMountBegin, content: `componentDidMount() {`, - linkAfter: [ - CLASS_DEFINE_CHUNK_NAME.Start, - CLASS_DEFINE_CHUNK_NAME.InsVar, - CLASS_DEFINE_CHUNK_NAME.InsMethod, - ], + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsMethod], }); next.chunks.push({ @@ -84,20 +81,15 @@ const pluginFactory: BuilderComponentPluginFactory = () => { fileType: FileType.JSX, name: RAX_CHUNK_NAME.ClassDidMountEnd, content: `}`, - linkAfter: [ - RAX_CHUNK_NAME.ClassDidMountStart, - RAX_CHUNK_NAME.ClassDidMountContent, - ], + linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin, RAX_CHUNK_NAME.ClassDidMountContent], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, - name: RAX_CHUNK_NAME.ClassRenderStart, + name: RAX_CHUNK_NAME.ClassRenderBegin, content: 'render() {', - linkAfter: [ - RAX_CHUNK_NAME.ClassDidMountEnd, - ], + linkAfter: [RAX_CHUNK_NAME.ClassDidMountEnd], }); next.chunks.push({ @@ -105,7 +97,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { fileType: FileType.JSX, name: RAX_CHUNK_NAME.ClassRenderEnd, content: '}', - linkAfter: [RAX_CHUNK_NAME.ClassRenderStart, RAX_CHUNK_NAME.ClassRenderPre, RAX_CHUNK_NAME.ClassRenderJSX], + linkAfter: [RAX_CHUNK_NAME.ClassRenderBegin, RAX_CHUNK_NAME.ClassRenderPre, RAX_CHUNK_NAME.ClassRenderJSX], }); next.chunks.push({ @@ -116,6 +108,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, CLASS_DEFINE_CHUNK_NAME.End, diff --git a/packages/code-generator/src/plugins/component/rax/containerDataSource.ts b/packages/code-generator/src/plugins/component/rax/containerDataSource.ts deleted file mode 100644 index 72d7efd6b..000000000 --- a/packages/code-generator/src/plugins/component/rax/containerDataSource.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; - -import { generateCompositeType } from '../../../utils/compositeType'; - -import { - BuilderComponentPlugin, - BuilderComponentPluginFactory, - ChunkType, - FileType, - ICodeStruct, - IContainerInfo, -} from '../../../types'; - -type PluginConfig = { - fileType: string; -} - -const pluginFactory: BuilderComponentPluginFactory = (config?) => { - const cfg: PluginConfig = { - fileType: FileType.JSX, - ...config, - }; - - const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, - }; - - const ir = next.ir as IContainerInfo; - - if (ir.state) { - const state = ir.state; - const fields = Object.keys(state).map(stateName => { - const [isString, value] = generateCompositeType(state[stateName]); - return `${stateName}: ${isString ? `'${value}'` : value},`; - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: cfg.fileType, - name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, - content: `this.state = { ${fields.join('')} };`, - linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], - }); - } - - return next; - }; - return plugin; -}; - -export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/rax/containerInitState.ts b/packages/code-generator/src/plugins/component/rax/containerInitState.ts index 50984c961..8dc634c98 100644 --- a/packages/code-generator/src/plugins/component/rax/containerInitState.ts +++ b/packages/code-generator/src/plugins/component/rax/containerInitState.ts @@ -14,12 +14,12 @@ import { type PluginConfig = { fileType: string; implementType: 'inConstructor' | 'insMember' | 'hooks'; -} +}; const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, - implementType: 'inConstructor', + implementType: 'insMember', ...config, }; @@ -32,8 +32,9 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => if (ir.state) { const state = ir.state; - const fields = Object.keys(state).map(stateName => { - const [isString, value] = generateCompositeType(state[stateName]); + const fields = Object.keys(state).map((stateName) => { + // TODO: 这里用什么 handlers? + const [isString, value] = generateCompositeType(state[stateName], {}); return `${stateName}: ${isString ? `'${value}'` : value},`; }); @@ -54,6 +55,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], }); } + // TODO: hooks state?? } return next; diff --git a/packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts b/packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts index 61eea0724..019383755 100644 --- a/packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts +++ b/packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts @@ -54,11 +54,11 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => content: ` this._dataSourceEngine.reloadDataSource(); `, - linkAfter: [RAX_CHUNK_NAME.ClassDidMountStart], + linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin], }); const dataSource = isContainerSchema(pre.ir) ? pre.ir.dataSource : null; - const dataSourceItems: DataSourceConfig[] = dataSource && dataSource.list || []; + const dataSourceItems: DataSourceConfig[] = (dataSource && dataSource.list) || []; next.chunks.push({ type: ChunkType.STRING, diff --git a/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts b/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts index aa9763c23..5fda869f2 100644 --- a/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts +++ b/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts @@ -1,4 +1,4 @@ -import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; +import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, @@ -11,7 +11,7 @@ import { RAX_CHUNK_NAME } from './const'; type PluginConfig = { fileType: string; -} +}; const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { @@ -24,7 +24,17 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => ...pre, }; - // TODO: utils 怎么注入? + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: COMMON_CHUNK_NAME.InternalDepsImport, + // TODO: 下面这个路径有没有更好的方式来获取?而非写死 + content: ` + import __$$projectUtils from '../../utils'; + `, + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], + }); + next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, @@ -33,18 +43,29 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); - + // TODO: Page methods... next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + + // 绑定下上下文,这样在所有的 utils 里面都能通过 this.xxx 来访问上下文了 + // TODO: 要不要优化为通过 Proxy 的方式懒绑定? content: ` _defineUtils() { - return {}; + const utils = { + ...__$$projectUtils, + }; + + Object.entries(utils).forEach(([name, util]) => { + if (typeof util === 'function') { + utils[name] = util.bind(this._context); + } + }); + + return utils; }`, - linkAfter: [ - RAX_CHUNK_NAME.ClassRenderEnd - ], + linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], }); return next; diff --git a/packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts b/packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts index 65d411242..cc5c8202f 100644 --- a/packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts +++ b/packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts @@ -1,10 +1,7 @@ import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; import { RAX_CHUNK_NAME } from './const'; -import { - getFuncExprBody, - transformFuncExpr2MethodMember, -} from '../../../utils/jsExpression'; +import { getFuncExprBody, transformFuncExpr2MethodMember } from '../../../utils/jsExpression'; import { BuilderComponentPlugin, @@ -22,7 +19,7 @@ type PluginConfig = { fileType: string; exportNameMapping: Record; normalizeNameMapping: Record; -} +}; const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { @@ -37,50 +34,53 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => ...pre, }; - const ir = next.ir as IContainerInfo; + // TODO: Rax 程序的生命周期暂未明确,此处先屏蔽 + // @see https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5 - if (ir.lifeCycles) { - const lifeCycles = ir.lifeCycles; - const chunks = Object.keys(lifeCycles).map(lifeCycleName => { - const normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName; - const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName; - if (normalizeName === 'constructor') { - return { - type: ChunkType.STRING, - fileType: cfg.fileType, - name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, - content: getFuncExprBody( - (lifeCycles[lifeCycleName] as IJSExpression).value, - ), - linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]], - }; - } - if (normalizeName === 'render') { - return { - type: ChunkType.STRING, - fileType: cfg.fileType, - name: RAX_CHUNK_NAME.ClassRenderPre, - content: getFuncExprBody( - (lifeCycles[lifeCycleName] as IJSExpression).value, - ), - linkAfter: [RAX_CHUNK_NAME.ClassRenderStart], - }; - } + // const ir = next.ir as IContainerInfo; - return { - type: ChunkType.STRING, - fileType: cfg.fileType, - name: CLASS_DEFINE_CHUNK_NAME.InsMethod, - content: transformFuncExpr2MethodMember( - exportName, - (lifeCycles[lifeCycleName] as IJSExpression).value, - ), - linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], - }; - }); + // if (ir.lifeCycles) { + // const lifeCycles = ir.lifeCycles; + // const chunks = Object.keys(lifeCycles).map(lifeCycleName => { + // const normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName; + // const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName; + // if (normalizeName === 'constructor') { + // return { + // type: ChunkType.STRING, + // fileType: cfg.fileType, + // name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, + // content: getFuncExprBody( + // (lifeCycles[lifeCycleName] as IJSExpression).value, + // ), + // linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]], + // }; + // } + // if (normalizeName === 'render') { + // return { + // type: ChunkType.STRING, + // fileType: cfg.fileType, + // name: RAX_CHUNK_NAME.ClassRenderPre, + // content: getFuncExprBody( + // (lifeCycles[lifeCycleName] as IJSExpression).value, + // ), + // linkAfter: [RAX_CHUNK_NAME.ClassRenderStart], + // }; + // } - next.chunks.push.apply(next.chunks, chunks); - } + // return { + // type: ChunkType.STRING, + // fileType: cfg.fileType, + // name: CLASS_DEFINE_CHUNK_NAME.InsMethod, + // content: transformFuncExpr2MethodMember( + // exportName, + // (lifeCycles[lifeCycleName] as IJSExpression).value, + // ), + // linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], + // }; + // }); + + // next.chunks.push.apply(next.chunks, chunks); + // } return next; }; diff --git a/packages/code-generator/src/plugins/component/rax/containerMethod.ts b/packages/code-generator/src/plugins/component/rax/containerMethod.ts deleted file mode 100644 index 9bba467da..000000000 --- a/packages/code-generator/src/plugins/component/rax/containerMethod.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; - -import { transformFuncExpr2MethodMember } from '../../../utils/jsExpression'; - -import { - BuilderComponentPlugin, - BuilderComponentPluginFactory, - ChunkType, - FileType, - ICodeChunk, - ICodeStruct, - IContainerInfo, - IJSExpression, -} from '../../../types'; - -type PluginConfig = { - fileType: string; -} - -const pluginFactory: BuilderComponentPluginFactory = (config?) => { - const cfg: PluginConfig = { - fileType: FileType.JSX, - ...config, - }; - - const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, - }; - - const ir = next.ir as IContainerInfo; - - if (ir.methods) { - const methods = ir.methods; - const chunks = Object.keys(methods).map(methodName => ({ - type: ChunkType.STRING, - fileType: cfg.fileType, - name: CLASS_DEFINE_CHUNK_NAME.InsMethod, - content: transformFuncExpr2MethodMember( - methodName, - (methods[methodName] as IJSExpression).value, - ), - linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], - })); - - next.chunks.push.apply(next.chunks, chunks); - } - - return next; - }; - return plugin; -}; - -export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/rax/containerMethods.ts b/packages/code-generator/src/plugins/component/rax/containerMethods.ts new file mode 100644 index 000000000..b13c81071 --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/containerMethods.ts @@ -0,0 +1,80 @@ +import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IContainerInfo, +} from '../../../types'; + +import { RAX_CHUNK_NAME } from './const'; + +type PluginConfig = { + fileType: string; +}; + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, + }; + + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IContainerInfo; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: ` + _methods = this._defineMethods(); + `, + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.MethodsBegin, + content: ` + _defineMethods() { + return ({ + `, + linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd, CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.MethodsEnd, + content: ` + }); + } + `, + linkAfter: [RAX_CHUNK_NAME.MethodsBegin, RAX_CHUNK_NAME.MethodsContent], + }); + + if (ir.methods && Object.keys(ir.methods).length > 0) { + Object.entries(ir.methods).forEach(([methodName, methodDefine]) => { + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.MethodsContent, + content: `${methodName}: (${methodDefine.value}),`, + linkAfter: [RAX_CHUNK_NAME.MethodsBegin], + }); + }); + } + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/rax/jsx.ts b/packages/code-generator/src/plugins/component/rax/jsx.ts index f41aa9de1..9b73f3ef1 100644 --- a/packages/code-generator/src/plugins/component/rax/jsx.ts +++ b/packages/code-generator/src/plugins/component/rax/jsx.ts @@ -3,25 +3,44 @@ import { BuilderComponentPluginFactory, ChunkType, FileType, + ICodeChunk, ICodeStruct, IContainerInfo, + isJSExpression, + isJSFunction, + JSExpression, + JSFunction, + NpmInfo, } from '../../../types'; import { RAX_CHUNK_NAME } from './const'; +import { COMMON_CHUNK_NAME } from '../../../const/generator'; -import { createReactNodeGenerator } from '../../../utils/nodeToJSX'; +import { createNodeGenerator, generateReactCtrlLine, generateString } from '../../../utils/nodeToJSX'; +import { generateExpression } from '../../../utils/jsExpression'; type PluginConfig = { fileType: string; -} +}; +// TODO: componentName 若并非大写字符打头,甚至并非是一个有效的 JS 标识符怎么办?? const pluginFactory: BuilderComponentPluginFactory = (config?) => { const cfg: PluginConfig = { fileType: FileType.JSX, ...config, }; - const generator = createReactNodeGenerator(); + const transformers = { + transformThis2Context, + transformJsExpr: (expr: string) => + isLiteralAtomicExpr(expr) ? expr : `__$$eval(() => (${transformThis2Context(expr)}))`, + transformLoopExpr: (expr: string) => `__$$evalArray(() => (${transformThis2Context(expr)}))`, + }; + + const handlers = { + expression: (input: JSExpression) => transformers.transformJsExpr(generateExpression(input)), + function: (input: JSFunction) => transformers.transformJsExpr(input.value || 'null'), + }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { @@ -29,22 +48,122 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => }; const ir = next.ir as IContainerInfo; + + // Rax 构建到小程序的时候,不能给组件起起别名,得直接引用,故这里将所有的别名替换掉 + // 先收集下所有的 alias 的映射 + const componentsNameAliasMap = new Map(); + next.chunks.forEach((chunk) => { + if (isImportAliasDefineChunk(chunk)) { + componentsNameAliasMap.set(chunk.ext.aliasName, chunk.ext.originalName); + } + }); + + const mapComponentNameToAliasOrKeepIt = (componentName: string) => + componentsNameAliasMap.get(componentName) || componentName; + + // 然后过滤掉所有的别名 chunks + next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk)); + + // 创建代码生成器 + const generator = createNodeGenerator( + { + string: generateString, + expression: (input) => [handlers.expression(input)], + function: (input) => [handlers.function(input)], + }, + [generateReactCtrlLine], + { + expression: (input) => (isJSExpression(input) ? handlers.expression(input) : ''), + function: (input) => (isJSFunction(input) ? handlers.function(input) : ''), + loopDataExpr: (input) => (typeof input === 'string' ? transformers.transformLoopExpr(input) : ''), + tagName: mapComponentNameToAliasOrKeepIt, + }, + ); + + // 生成 JSX 代码 const jsxContent = generator(ir); + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.ClassRenderPre, + content: `const __$$context = this._context;`, + linkAfter: [RAX_CHUNK_NAME.ClassRenderBegin], + }); + next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: RAX_CHUNK_NAME.ClassRenderJSX, content: `return ${jsxContent};`, - linkAfter: [ - RAX_CHUNK_NAME.ClassRenderStart, - RAX_CHUNK_NAME.ClassRenderPre, - ], + linkAfter: [RAX_CHUNK_NAME.ClassRenderBegin, RAX_CHUNK_NAME.ClassRenderPre], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: COMMON_CHUNK_NAME.CustomContent, + content: ` + + function __$$eval(expr) { + try { + return expr(); + } catch (err) { + console.warn('Failed to evaluate: ', expr, err); + } + } + + function __$$evalArray(expr) { + const res = __$$eval(expr); + return Array.isArray(res) ? res : []; + } + + `, + linkAfter: [COMMON_CHUNK_NAME.FileExport], }); return next; }; + return plugin; }; export default pluginFactory; + +function isImportAliasDefineChunk( + chunk: ICodeChunk, +): chunk is ICodeChunk & { + ext: { + aliasName: string; + originalName: string; + dependency: NpmInfo; + }; +} { + return ( + chunk.name === COMMON_CHUNK_NAME.ImportAliasDefine && + !!chunk.ext && + typeof chunk.ext.aliasName === 'string' && + typeof chunk.ext.originalName === 'string' && + !!(chunk.ext.dependency as NpmInfo | null)?.componentName + ); +} + +/** + * 判断是否是原子类型的表达式 + */ +function isLiteralAtomicExpr(expr: string): boolean { + return expr === 'null' || expr === 'undefined' || expr === 'true' || expr === 'false' || /^\d+$/.test(expr); +} + +/** + * 将所有的 this.xxx 替换为 __$$context.xxx + * @param expr + */ +function transformThis2Context(expr: string): string { + // TODO: 应该根据语法分析来搞 + // TODO: 如何替换自定义名字的循环变量?(generateReactCtrlLine) + return expr + .replace(/\bthis\.item\./, () => 'item.') + .replace(/\bthis\.index\./, () => 'index.') + .replace(/\bthis\./, () => '__$$context.'); +} diff --git a/packages/code-generator/src/plugins/component/react/containerClass.ts b/packages/code-generator/src/plugins/component/react/containerClass.ts index f0e591583..82c042533 100644 --- a/packages/code-generator/src/plugins/component/react/containerClass.ts +++ b/packages/code-generator/src/plugins/component/react/containerClass.ts @@ -30,6 +30,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], @@ -97,6 +98,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, CLASS_DEFINE_CHUNK_NAME.End, diff --git a/packages/code-generator/src/plugins/component/recore/pageDataSource.ts b/packages/code-generator/src/plugins/component/recore/pageDataSource.ts index 87d496aaf..bb7d91857 100644 --- a/packages/code-generator/src/plugins/component/recore/pageDataSource.ts +++ b/packages/code-generator/src/plugins/component/recore/pageDataSource.ts @@ -7,16 +7,15 @@ import { FileType, ICodeStruct, IContainerInfo, - IJSExpression, CompositeValue, + JSExpression, } from '../../../types'; import { generateCompositeType, handleStringValueDefault } from '../../../utils/compositeType'; import { generateExpression } from '../../../utils/jsExpression'; -function packJsExpression(exp: unknown): string { - const expression = exp as IJSExpression; - const funcStr = generateExpression(expression); +function packJsExpression(exp: JSExpression): string { + const funcStr = generateExpression(exp); return `function() { return (${funcStr}); }`; } @@ -29,14 +28,11 @@ const pluginFactory: BuilderComponentPluginFactory = () => { const ir = next.ir as IContainerInfo; if (ir.dataSource) { const { dataSource } = ir; - const { - list, - ...rest - } = dataSource; + const { list, ...rest } = dataSource; let attrs: string[] = []; - const extConfigs = Object.keys(rest).map(extConfigName => { + const extConfigs = Object.keys(rest).map((extConfigName) => { const value = (rest as Record)[extConfigName]; const [isString, valueStr] = generateCompositeType(value); return `${extConfigName}: ${isString ? `'${valueStr}'` : valueStr}`; @@ -44,9 +40,11 @@ const pluginFactory: BuilderComponentPluginFactory = () => { attrs = [...attrs, ...extConfigs]; - const listProp = handleStringValueDefault(generateCompositeType(list as unknown as CompositeValue, { - expression: packJsExpression, - })); + const listProp = handleStringValueDefault( + generateCompositeType((list as unknown) as CompositeValue, { + expression: packJsExpression, + }), + ); attrs.push(`list: ${listProp}`); diff --git a/packages/code-generator/src/plugins/project/constants.ts b/packages/code-generator/src/plugins/project/constants.ts index 5c02e9711..cf798686b 100644 --- a/packages/code-generator/src/plugins/project/constants.ts +++ b/packages/code-generator/src/plugins/project/constants.ts @@ -29,6 +29,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, ], }); @@ -42,6 +43,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/entry.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/entry.ts index 0cf3918a7..9d0e9fcb3 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/plugins/entry.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/entry.ts @@ -45,6 +45,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/router.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/router.ts index b4f2d718f..56e38d45f 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/plugins/router.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/router.ts @@ -39,7 +39,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { children: [ ${ir.routes .map( - route => ` + (route) => ` { path: '${route.path}', component: ${route.componentName}, @@ -54,6 +54,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], }); @@ -69,6 +70,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.FileUtilDefine, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileMainContent, ], diff --git a/packages/code-generator/src/plugins/project/framework/rax/plugins/entry.ts b/packages/code-generator/src/plugins/project/framework/rax/plugins/entry.ts index 09bc07af8..e586ac2e2 100644 --- a/packages/code-generator/src/plugins/project/framework/rax/plugins/entry.ts +++ b/packages/code-generator/src/plugins/project/framework/rax/plugins/entry.ts @@ -38,6 +38,7 @@ runApp(appConfig); linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], diff --git a/packages/code-generator/src/plugins/project/i18n.ts b/packages/code-generator/src/plugins/project/i18n.ts index cada1fd96..2ecc68cfb 100644 --- a/packages/code-generator/src/plugins/project/i18n.ts +++ b/packages/code-generator/src/plugins/project/i18n.ts @@ -17,7 +17,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { const ir = next.ir as IProjectInfo; if (ir.i18n) { - const [, i18nStr] = generateCompositeType(ir.i18n); + const [, i18nStr] = generateCompositeType(ir.i18n, {}); next.chunks.push({ type: ChunkType.STRING, @@ -36,6 +36,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], @@ -54,6 +55,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, diff --git a/packages/code-generator/src/plugins/project/utils.ts b/packages/code-generator/src/plugins/project/utils.ts index 32e7c5d17..5cdb21e81 100644 --- a/packages/code-generator/src/plugins/project/utils.ts +++ b/packages/code-generator/src/plugins/project/utils.ts @@ -28,24 +28,26 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, ], }); - ir.utils.forEach(util => { + ir.utils.forEach((util) => { if (util.type === 'function') { next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileVarDefine, content: ` - const ${util.name} = ${util.content}; + const ${util.name} = ${util.content.value}; `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, ], }); } @@ -54,12 +56,11 @@ const pluginFactory: BuilderComponentPluginFactory = () => { type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileExport, - content: ` - ${util.name}, - `, + content: `${util.name},`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, @@ -77,6 +78,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileMainContent, diff --git a/packages/code-generator/src/solutions/rax-app.ts b/packages/code-generator/src/solutions/rax-app.ts index 0a1a55154..230c93813 100644 --- a/packages/code-generator/src/solutions/rax-app.ts +++ b/packages/code-generator/src/solutions/rax-app.ts @@ -4,8 +4,8 @@ import { createProjectBuilder } from '../generator/ProjectBuilder'; import esModule from '../plugins/common/esmodule'; import containerClass from '../plugins/component/rax/containerClass'; -import containerLifeCycle from '../plugins/component/rax/containerLifeCycle'; -import containerMethod from '../plugins/component/rax/containerMethod'; +import containerLifeCycles from '../plugins/component/rax/containerLifeCycle'; +import containerMethods from '../plugins/component/rax/containerMethods'; import containerInitState from '../plugins/component/rax/containerInitState'; import containerInjectContext from '../plugins/component/rax/containerInjectContext'; import containerInjectDataSourceEngine from '../plugins/component/rax/containerInjectDataSourceEngine'; @@ -27,29 +27,27 @@ export default function createIceJsProjectBuilder(): IProjectBuilder { plugins: { components: [ commonDeps(), - esModule({ - fileType: 'jsx', - }), + esModule({ fileType: 'jsx' }), containerClass(), - containerInjectUtils(), containerInitState(), - containerLifeCycle(), - containerMethod(), + containerMethods(), + containerInjectContext(), + containerInjectDataSourceEngine(), + containerInjectUtils(), + containerLifeCycles(), jsx(), css(), ], pages: [ commonDeps(), - esModule({ - fileType: 'jsx', - }), + esModule({ fileType: 'jsx' }), containerClass(), containerInitState(), + containerMethods(), containerInjectContext(), containerInjectDataSourceEngine(), containerInjectUtils(), - containerLifeCycle(), - containerMethod(), + containerLifeCycles(), jsx(), css(), ], diff --git a/packages/code-generator/src/types/core.ts b/packages/code-generator/src/types/core.ts index 6eb58ff17..2ddd14d46 100644 --- a/packages/code-generator/src/types/core.ts +++ b/packages/code-generator/src/types/core.ts @@ -1,12 +1,6 @@ -import { - IBasicSchema, - IParseResult, - IProjectSchema, - IResultDir, - IResultFile, - IComponentNodeItem, - IJSExpression, -} from './index'; +import { JSExpression, JSFunction, NodeSchema } from '@ali/lowcode-types'; +import { CustomHandlerSet } from '../utils/compositeType'; +import { IBasicSchema, IParseResult, IProjectSchema, IResultDir, IResultFile, IComponentNodeItem } from './index'; export enum FileType { CSS = 'css', @@ -42,6 +36,7 @@ export interface ICodeChunk { subModule?: string; content: ChunkContent; linkAfter: string[]; + ext?: Record; } export interface IBaseCodeStruct { @@ -54,17 +49,12 @@ export interface ICodeStruct extends IBaseCodeStruct { chunks: ICodeChunk[]; } -export type BuilderComponentPlugin = ( - initStruct: ICodeStruct, -) => Promise; +export type BuilderComponentPlugin = (initStruct: ICodeStruct) => Promise; export type BuilderComponentPluginFactory = (config?: T) => BuilderComponentPlugin; export interface IChunkBuilder { - run( - ir: any, - initialStructure?: ICodeStruct, - ): Promise<{ chunks: ICodeChunk[][] }>; + run(ir: any, initialStructure?: ICodeStruct): Promise<{ chunks: ICodeChunk[][] }>; getPlugins(): BuilderComponentPlugin[]; addPlugin(plugin: BuilderComponentPlugin): void; } @@ -81,10 +71,7 @@ export interface ICompiledModule { export interface IModuleBuilder { generateModule(input: unknown): Promise; generateModuleCode(schema: IBasicSchema | string): Promise; - linkCodeChunks( - chunks: Record, - fileName: string, - ): IResultFile[]; + linkCodeChunks(chunks: Record, fileName: string): IResultFile[]; addPlugin(plugin: BuilderComponentPlugin): void; } @@ -155,21 +142,23 @@ export enum PIECE_TYPE { ATTR = 'NodeCodePieceAttr', CHILDREN = 'NodeCodePieceChildren', AFTER = 'NodeCodePieceAfter', -}; +} export interface CodePiece { value: string; type: PIECE_TYPE; } +// TODO: 这个 HandlerSet 和 CustomHandlerSet 为啥定义还不一样? export interface HandlerSet { string?: (input: string) => T[]; - expression?: (input: IJSExpression) => T[]; - node?: (input: IComponentNodeItem) => T[]; + expression?: (input: JSExpression) => T[]; + function?: (input: JSFunction) => T[]; + node?: (input: NodeSchema) => T[]; common?: (input: unknown) => T[]; } -export type ExtGeneratorPlugin = (nodeItem: IComponentNodeItem) => CodePiece[]; +export type ExtGeneratorPlugin = (nodeItem: IComponentNodeItem, handlers: CustomHandlerSet) => CodePiece[]; // export interface InteratorScope { // [$item: string]: string; // $item 默认取值 "item" diff --git a/packages/code-generator/src/types/intermediate.ts b/packages/code-generator/src/types/intermediate.ts index c41f4d7c9..c9052ac96 100644 --- a/packages/code-generator/src/types/intermediate.ts +++ b/packages/code-generator/src/types/intermediate.ts @@ -16,10 +16,11 @@ export interface IParseResult { project?: IProjectInfo; } -export interface IContainerInfo extends IContainerNodeItem, IWithDependency { - containerType: string; - moduleName: string; -} +export type IContainerInfo = IContainerNodeItem & + IWithDependency & { + containerType: string; + moduleName: string; + }; export interface IWithDependency { deps?: IDependency[]; diff --git a/packages/code-generator/src/types/schema.ts b/packages/code-generator/src/types/schema.ts index 68226e8ab..9cd634cb7 100644 --- a/packages/code-generator/src/types/schema.ts +++ b/packages/code-generator/src/types/schema.ts @@ -1,5 +1,19 @@ -// 搭建基础协议、搭建入料协议的数据规范 -import { IExternalDependency } from './index'; +import { + ProjectSchema, + CompositeObject, + JSExpression, + JSONObject, + NpmInfo, + NodeData, + NodeSchema, + UtilItem, + PageSchema, + BlockSchema, + ComponentSchema, + DataSourceConfig, +} from '@ali/lowcode-types'; + +export * from '@ali/lowcode-types'; /** * 搭建基础协议 - 函数表达式 @@ -7,37 +21,12 @@ import { IExternalDependency } from './index'; * @export * @interface IJSExpression */ -export interface IJSExpression { - type: 'JSExpression'; - value: string; - [extConfigName: string]: any; -} +export type IJSExpression = JSExpression; // JSON 基本类型 -export interface IJSONObject { - [key: string]: JSONValue; -} +export type IJSONObject = JSONObject; -export type JSONValue = - | boolean - | string - | number - | null - | JSONArray - | IJSONObject; -export type JSONArray = JSONValue[]; - -export type CompositeArray = CompositeValue[]; -export interface ICompositeObject { - [key: string]: CompositeValue; -} - -// 复合类型 -export type CompositeValue = - | JSONValue - | IJSExpression - | CompositeArray - | ICompositeObject; +export type ICompositeObject = CompositeObject; /** * 搭建基础协议 - 多语言描述 @@ -57,38 +46,20 @@ export interface II18nMap { * @export * @interface IBasicSchema */ -export interface IBasicSchema { - version: string; // 当前协议版本号 - componentsMap: IComponentsMapItem[]; // 组件映射关系 - componentsTree: Array; // 描述模版/页面/区块/低代码业务组件的组件树 低代码业务组件树描述,固定长度为1,且顶层为低代码业务组件容器描述 - utils?: IUtilItem[]; // 工具类扩展映射关系 低代码业务组件不包含 - i18n?: II18nMap; // 国际化语料 -} +export interface IBasicSchema extends ProjectSchema {} export interface IProjectSchema extends IBasicSchema { + // TODO: 下面的几个值真的需要吗?.... constants: Record; // 应用范围内的全局常量; css: string; // 应用范围内的全局样式; config: IAppConfig; // 当前应用配置信息 meta: IAppMeta; // 当前应用元数据信息 } -/** - * 搭建基础协议 - 单个组件描述 - * - * @export - * @interface IComponentsMapItem - */ -export interface IComponentsMapItem extends IExternalDependency { - componentName: string; // 组件名称 -} +export interface IComponentsMapItem extends NpmInfo {} -export interface IUtilItem { - name: string; - type: 'npm' | 'tnpm' | 'function'; - content: IExternalDependency | IJSExpression; -} - -export type ChildNodeItem = string | IJSExpression | IComponentNodeItem; +export type IUtilItem = UtilItem; +export type ChildNodeItem = NodeData; export type ChildNodeType = ChildNodeItem | ChildNodeItem[]; /** @@ -98,18 +69,7 @@ export type ChildNodeType = ChildNodeItem | ChildNodeItem[]; * @export * @interface IComponentNodeItem */ -export interface IComponentNodeItem { - // TODO: 不需要 id 字段,暂时简单兼容 - id?: string; - componentName: string; // 组件名称 必填、首字母大写 - props: { - [propName: string]: CompositeValue; // 业务属性 - }; // 组件属性对象 - condition?: CompositeValue; // 渲染条件 - loop?: CompositeValue; // 循环数据 - loopArgs?: [string, string]; // 循环迭代对象、索引名称 ["item", "index"] - children?: ChildNodeType; // 子节点 -} +export interface IComponentNodeItem extends NodeSchema {} /** * 搭建基础协议 - 单个容器节点描述 @@ -118,31 +78,7 @@ export interface IComponentNodeItem { * @interface IContainerNodeItem * @extends {IComponentNodeItem} */ -export interface IContainerNodeItem extends IComponentNodeItem { - componentName: 'Page' | 'Block' | 'Component'; // 'Page' | 'Block' | 'Component' 组件类型 必填、首字母大写 - fileName: string; // 文件名称 必填、英文 - state?: { - [stateName: string]: CompositeValue; // 容器初始数据 - }; - css?: string; // 样式文件 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,在对应容器组件生成的 .jsx 文件中 import 引入; - /** - * LifeCycle - * • constructor(props, context) - * • 说明:初始化渲染时执行,常用于设置state值; - * • render() - * • 说明:执行于容器组件React Class的render方法最前,常用于计算变量挂载到this对象上,供props上属性绑定。此render()方法不需要设置return返回值。 - * • componentDidMount() - * • componentDidUpdate(prevProps, prevState, snapshot) - * • componentWillUnmount() - * • componentDidCatch(error, info) - */ - lifeCycles?: Record; // 生命周期Hook方法 - methods?: Record; // 自定义方法设置 - dataSource?: { - list: IDataSourceConfig[]; - }; // 异步数据源配置 - meta?: IBasicMeta | IPageMeta; -} +export type IContainerNodeItem = PageSchema | BlockSchema | ComponentSchema; /** * 搭建基础协议 - 数据源单个配置 @@ -150,69 +86,25 @@ export interface IContainerNodeItem extends IComponentNodeItem { * @export * @interface IDataSourceConfig */ -export interface IDataSourceConfig { - id: string; // 数据请求ID标识 - isInit: boolean; // 是否为初始数据 支持表达式 值为true时,将在组件初始化渲染时自动发送当前数据请求 - type: string; // 数据请求类型 'fetch' | 'mtop' | 'jsonp' | 'custom' - requestHandler?: IJSExpression; // 自定义扩展的外部请求处理器 仅type='custom'时生效 - options?: IFetchOptions; // 请求参数配置 每种请求类型对应不同参数 - dataHandler?: IJSExpression; // 数据结果处理函数,形如:(data, err) => Object -} - -/** - * 搭建基础协议 - 请求参数配置 - * - * @export - * @interface IFetchOptions - */ -export interface IFetchOptions { - url: string; // 请求地址 支持表达式 - params?: { - // 请求参数 - [key: string]: any; - }; - method: 'GET' | 'POST'; - isCors?: boolean; // 是否支持跨域,对应credentials = 'include' - timeout?: number; // 超时时长 - headers?: { - // 自定义请求头 - [key: string]: string; - }; - [extConfigName: string]: any; -} +export interface IDataSourceConfig extends DataSourceConfig {} +// TODO... export interface IBasicMeta { title: string; // 标题描述 } +// TODO... export interface IPageMeta extends IBasicMeta { router: string; // 页面路由 spmb?: string; // spm } -// "theme": { -// //for Fusion use dpl defined -// "package": "@alife/theme-fusion", -// "version": "^0.1.0", - -// //for Antd use variable -// "primary": "#ff9966" -// } - -// "layout": { -// "componentName": "BasicLayout", -// "props": { -// "logo": "...", -// "name": "测试网站" -// }, -// }, - export interface IAppConfig { - sdkVersion: string; // 渲染模块版本 - historyMode: 'brower' | 'hash'; // 浏览器路由:brower 哈希路由:hash - targetRootID: string; // 渲染根节点 ID - layout: IComponentNodeItem; - theme: object; // 主题配置,根据接入的主题模块不同 + sdkVersion?: string; // 渲染模块版本 + historyMode?: 'browser' | 'hash'; // 浏览器路由:browser 哈希路由:hash + targetRootID?: string; // 渲染根节点 ID + layout?: IComponentNodeItem; + theme?: object; // 主题配置,根据接入的主题模块不同 } export interface IAppMeta { diff --git a/packages/code-generator/src/utils/compositeType.ts b/packages/code-generator/src/utils/compositeType.ts index b62b2afe3..6c4e4875a 100644 --- a/packages/code-generator/src/utils/compositeType.ts +++ b/packages/code-generator/src/utils/compositeType.ts @@ -1,37 +1,43 @@ -import { CompositeArray, CompositeValue, ICompositeObject } from '../types'; -import { generateExpression, isJsExpression } from './jsExpression'; +import { + CompositeArray, + CompositeValue, + CompositeObject, + JSExpression, + JSFunction, + JSONArray, + JSONObject, + isJSExpression, + isJSFunction, + isJSSlot, + JSSlot, + NodeSchema, + NodeData, +} from '../types'; +import { generateExpression, generateFunction } from './jsExpression'; -type CustomHandler = (data: unknown) => string; -interface CustomHandlerSet { - boolean?: CustomHandler; - number?: CustomHandler; - string?: CustomHandler; - array?: CustomHandler; - object?: CustomHandler; - expression?: CustomHandler; +export interface CustomHandlerSet { + boolean?: (bool: boolean) => string; + number?: (num: number) => string; + string?: (str: string) => string; + array?: (arr: JSONArray | CompositeArray) => string; + object?: (obj: JSONObject | CompositeObject) => string; + expression?: (jsExpr: JSExpression) => string; + function?: (jsFunc: JSFunction) => string; + slot?: (jsSlot: JSSlot) => string; + node?: (node: NodeSchema) => string; + loopDataExpr?: (loopDataExpr: string) => string; + conditionExpr?: (conditionExpr: string) => string; + tagName?: (tagName: string) => string; } -function generateArray( - value: CompositeArray, - handlers: CustomHandlerSet = {}, -): string { - const body = value.map(v => generateUnknownType(v, handlers)).join(','); +function generateArray(value: CompositeArray, handlers: CustomHandlerSet): string { + const body = value.map((v) => generateUnknownType(v, handlers)).join(','); return `[${body}]`; } -function generateObject( - value: ICompositeObject, - handlers: CustomHandlerSet = {}, -): string { - if (isJsExpression(value)) { - if (handlers.expression) { - return handlers.expression(value); - } - return generateExpression(value); - } - +function generateObject(value: CompositeObject, handlers: CustomHandlerSet): string { const body = Object.keys(value) - .map(key => { + .map((key) => { const v = generateUnknownType(value[key], handlers); return `${key}: ${v}`; }) @@ -40,39 +46,65 @@ function generateObject( return `{${body}}`; } -export function generateUnknownType( - value: CompositeValue, - handlers: CustomHandlerSet = {}, -): string { +export function generateUnknownType(value: CompositeValue, handlers: CustomHandlerSet = {}): string { if (Array.isArray(value)) { if (handlers.array) { return handlers.array(value); } - return generateArray(value as CompositeArray, handlers); + return generateArray(value, handlers); } else if (typeof value === 'object') { + if (value === null) { + return 'null'; + } + + if (isJSExpression(value)) { + if (handlers.expression) { + return handlers.expression(value); + } + return generateExpression(value); + } + + if (isJSFunction(value)) { + if (handlers.function) { + return handlers.function(value); + } + return generateFunction(value); + } + + if (isJSSlot(value)) { + if (handlers.slot) { + return handlers.slot(value); + } + + return generateSlot(value, handlers); + } + if (handlers.object) { return handlers.object(value); } - return generateObject(value as ICompositeObject, handlers); + + return generateObject(value, handlers); } else if (typeof value === 'string') { if (handlers.string) { return handlers.string(value); } - return `'${value}'`; + + return JSON.stringify(value); } else if (typeof value === 'number' && handlers.number) { return handlers.number(value); } else if (typeof value === 'boolean' && handlers.boolean) { return handlers.boolean(value); + } else if (typeof value === 'undefined') { + return 'undefined'; } - return `${value}`; + + return JSON.stringify(value); } -export function generateCompositeType( - value: CompositeValue, - handlers: CustomHandlerSet = {}, -): [boolean, string] { +export function generateCompositeType(value: CompositeValue, handlers: CustomHandlerSet = {}): [boolean, string] { const result = generateUnknownType(value, handlers); + // TODO:什么场景下会返回这样的字符串?? if (result.substr(0, 1) === "'" && result.substr(-1, 1) === "'") { return [true, result.substring(1, result.length - 1)]; } @@ -84,5 +116,50 @@ export function handleStringValueDefault([isString, result]: [boolean, string]) if (isString) { return `'${result}'`; } + return result; } + +function generateSlot({ title, params, value }: JSSlot, handlers: CustomHandlerSet): string { + return [ + title && generateSingleLineComment(title), + `(`, + ...(params || []), + `) => (`, + ...(!value + ? ['null'] + : !Array.isArray(value) + ? [generateNodeData(value, handlers)] + : value.map((node) => generateNodeData(node, handlers))), + `)`, + ] + .filter(Boolean) + .join(''); +} + +function generateNodeData(node: NodeData, handlers: CustomHandlerSet): string { + if (typeof node === 'string') { + if (handlers.string) { + return handlers.string(node); + } + + return JSON.stringify(node); + } + + if (isJSExpression(node)) { + if (handlers.expression) { + return handlers.expression(node); + } + return generateExpression(node); + } + + if (!handlers.node) { + throw new Error('cannot handle NodeSchema, handlers.node is missing'); + } + + return handlers.node(node); +} + +function generateSingleLineComment(commentText: string): string { + return '/* ' + commentText.split('\n').join(' ').replace(/\*\//g, '*-/') + '*/'; +} diff --git a/packages/code-generator/src/utils/jsExpression.ts b/packages/code-generator/src/utils/jsExpression.ts index 9c8541692..b598ae2e3 100644 --- a/packages/code-generator/src/utils/jsExpression.ts +++ b/packages/code-generator/src/utils/jsExpression.ts @@ -1,39 +1,6 @@ -import traverse from '@babel/traverse'; -import * as parser from '@babel/parser'; -import { CodeGeneratorError, IJSExpression } from '../types'; - -let count = 0; - -function test(functionBody: string) { - console.log(functionBody); - console.log('---->'); - try { - const parseResult = parser.parse(functionBody); - // console.log(JSON.stringify(parseResult)); - traverse(parseResult, { - enter(path) { - console.log('path: ', JSON.stringify(path)); - } - }); - - if (count === 0) { - count++; - - test('this.aaa && this.bbb'); - } - } catch (error) { - // console.log('Error'); - console.log(error.message); - } - console.log('====================='); -} - -export function transformFuncExpr2MethodMember( - methodName: string, - functionBody: string, -): string { - // test(functionBody); +import { CodeGeneratorError, isJSExpression, isJSFunction } from '../types'; +export function transformFuncExpr2MethodMember(methodName: string, functionBody: string): string { const args = getFuncExprArguments(functionBody); const body = getFuncExprBody(functionBody); @@ -56,8 +23,6 @@ export function getFuncExprBody(functionBody: string) { const start = functionBody.indexOf('{'); const end = functionBody.lastIndexOf('}'); - // test(functionBody); - if (start < 0 || end < 0 || end < start) { throw new CodeGeneratorError('JSExpression has no valid body.'); } @@ -67,19 +32,18 @@ export function getFuncExprBody(functionBody: string) { } export function generateExpression(value: any): string { - if (value && (value as IJSExpression).type === 'JSExpression') { - // test((value as IJSExpression).value); - - return (value as IJSExpression).value || 'null'; + if (isJSExpression(value)) { + return value.value || 'null'; } throw new CodeGeneratorError('Not a JSExpression'); } -export function isJsExpression(value: any): boolean { - return ( - value && - typeof value === 'object' && - (value as IJSExpression).type === 'JSExpression' - ); +// TODO: 这样真的可以吗? +export function generateFunction(value: any): string { + if (isJSFunction(value)) { + return value.value || 'null'; + } + + throw new CodeGeneratorError('Not a isJSFunction'); } diff --git a/packages/code-generator/src/utils/nodeToJSX.ts b/packages/code-generator/src/utils/nodeToJSX.ts index db5f87c1d..a6fe8614a 100644 --- a/packages/code-generator/src/utils/nodeToJSX.ts +++ b/packages/code-generator/src/utils/nodeToJSX.ts @@ -1,59 +1,73 @@ +import _ from 'lodash'; import { - ChildNodeType, - IComponentNodeItem, - IJSExpression, - ChildNodeItem, - CodeGeneratorError, PIECE_TYPE, + CodeGeneratorError, CodePiece, HandlerSet, ExtGeneratorPlugin, + isJSExpression, + isJSFunction, + NodeData, + CompositeValue, + NodeSchema, } from '../types'; -import { generateCompositeType } from './compositeType'; +import { CustomHandlerSet, generateCompositeType } from './compositeType'; import { generateExpression } from './jsExpression'; // tslint:disable-next-line: no-empty const noop = () => []; -export function handleChildren( - children: ChildNodeType, - handlers: HandlerSet, -): T[] { +export function handleChildren(children: NodeData | NodeData[], handlers: HandlerSet): string[] { if (Array.isArray(children)) { - const list: ChildNodeItem[] = children as ChildNodeItem[]; - return list - .map(child => handleChildren(child, handlers)) - .reduce((p, c) => p.concat(c), []); + return children.map((child) => handleChildren(child, handlers)).reduce((p, c) => p.concat(c), []); } else if (typeof children === 'string') { const handler = handlers.string || handlers.common || noop; - return handler(children as string); - } else if ((children as IJSExpression).type === 'JSExpression') { + return handler(children); + } else if (isJSExpression(children)) { const handler = handlers.expression || handlers.common || noop; - return handler(children as IJSExpression); + return ['{', ...handler(children), '}']; + } else if (isJSFunction(children)) { + const handler = handlers.function || handlers.common || noop; + return handler(children); } else { const handler = handlers.node || handlers.common || noop; - return handler(children as IComponentNodeItem); + return handler(children); } } -export function generateAttr(attrName: string, attrValue: any): CodePiece[] { +export function generateAttr(attrName: string, attrValue: CompositeValue, handlers: CustomHandlerSet): CodePiece[] { if (attrName === 'initValue' || attrName === 'labelCol') { return []; } - const [isString, valueStr] = generateCompositeType(attrValue); - return [{ - value: `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`, - type: PIECE_TYPE.ATTR, - }]; + const [isString, valueStr] = generateCompositeType(attrValue, handlers); + return [ + { + value: `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`, + type: PIECE_TYPE.ATTR, + }, + ]; } -export function generateAttrs(nodeItem: IComponentNodeItem): CodePiece[] { +export function generateAttrs(nodeItem: NodeSchema, handlers: CustomHandlerSet): CodePiece[] { const { props } = nodeItem; + let pieces: CodePiece[] = []; - Object.keys(props).forEach((propName: string) => - pieces = pieces.concat(generateAttr(propName, props[propName])), - ); + if (props) { + if (!Array.isArray(props)) { + Object.keys(props).forEach((propName: string) => { + pieces = pieces.concat(generateAttr(propName, props[propName], handlers)); + }); + } else { + props.forEach((prop) => { + if (prop.name && !prop.spread) { + pieces = pieces.concat(generateAttr(prop.name, prop.value, handlers)); + } + + // TODO: 处理 spread 场景() + }); + } + } return pieces; } @@ -62,10 +76,11 @@ export function mapNodeName(src: string): string { if (src === 'Div') { return 'div'; } + return src; } -export function generateBasicNode(nodeItem: IComponentNodeItem): CodePiece[] { +export function generateBasicNode(nodeItem: NodeSchema): CodePiece[] { const pieces: CodePiece[] = []; pieces.push({ value: mapNodeName(nodeItem.componentName), @@ -75,20 +90,23 @@ export function generateBasicNode(nodeItem: IComponentNodeItem): CodePiece[] { return pieces; } -export function generateReactCtrlLine(nodeItem: IComponentNodeItem): CodePiece[] { +export function generateReactCtrlLine(nodeItem: NodeSchema, handlers: CustomHandlerSet): CodePiece[] { const pieces: CodePiece[] = []; - if (nodeItem.loop && nodeItem.loopArgs) { - let loopDataExp; - if ((nodeItem.loop as IJSExpression).type === 'JSExpression') { - loopDataExp = `(${(nodeItem.loop as IJSExpression).value})`; - } else { - loopDataExp = JSON.stringify(nodeItem.loop); - } + if (nodeItem.loop) { + const loopItemName = nodeItem.loopArgs?.[0] || 'item'; + const loopIndexName = nodeItem.loopArgs?.[1] || 'index'; + + // TODO: 静态的值可以抽离出来? + const loopDataExpr = (handlers.loopDataExpr || _.identity)( + isJSExpression(nodeItem.loop) ? `(${nodeItem.loop.value})` : `(${JSON.stringify(nodeItem.loop)})`, + ); + pieces.unshift({ - value: `${loopDataExp}.map((${nodeItem.loopArgs[0]}, ${nodeItem.loopArgs[1]}) => (`, + value: `${loopDataExpr}.map((${loopItemName}, ${loopIndexName}) => (`, type: PIECE_TYPE.BEFORE, }); + pieces.push({ value: '))', type: PIECE_TYPE.AFTER, @@ -96,23 +114,26 @@ export function generateReactCtrlLine(nodeItem: IComponentNodeItem): CodePiece[] } if (nodeItem.condition) { - const [isString, value] = generateCompositeType(nodeItem.condition); + const [isString, value] = generateCompositeType(nodeItem.condition, handlers); + const conditionExpr = (handlers.conditionExpr || _.identity)(isString ? `'${value}'` : value); pieces.unshift({ - value: `(${isString ? `'${value}'` : value}) && (`, + value: `(${conditionExpr}) && (`, type: PIECE_TYPE.BEFORE, }); + pieces.push({ value: ')', type: PIECE_TYPE.AFTER, }); } - if (nodeItem.condition || (nodeItem.loop && nodeItem.loopArgs)) { + if (nodeItem.condition || nodeItem.loop) { pieces.unshift({ value: '{', type: PIECE_TYPE.BEFORE, }); + pieces.push({ value: '}', type: PIECE_TYPE.AFTER, @@ -122,30 +143,32 @@ export function generateReactCtrlLine(nodeItem: IComponentNodeItem): CodePiece[] return pieces; } -export function linkPieces(pieces: CodePiece[]): string { - if (pieces.filter(p => p.type === PIECE_TYPE.TAG).length !== 1) { +export function linkPieces(pieces: CodePiece[], handlers: CustomHandlerSet): 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 = pieces.filter(p => p.type === PIECE_TYPE.TAG)[0].value; + + const tagName = (handlers.tagName || _.identity)(tagsPieces[0].value); const beforeParts = pieces - .filter(p => p.type === PIECE_TYPE.BEFORE) - .map(p => p.value) + .filter((p) => p.type === PIECE_TYPE.BEFORE) + .map((p) => p.value) .join(''); const afterParts = pieces - .filter(p => p.type === PIECE_TYPE.AFTER) - .map(p => p.value) + .filter((p) => p.type === PIECE_TYPE.AFTER) + .map((p) => p.value) .join(''); const childrenParts = pieces - .filter(p => p.type === PIECE_TYPE.CHILDREN) - .map(p => p.value) + .filter((p) => p.type === PIECE_TYPE.CHILDREN) + .map((p) => p.value) .join(''); let attrsParts = pieces - .filter(p => p.type === PIECE_TYPE.ATTR) - .map(p => p.value) + .filter((p) => p.type === PIECE_TYPE.ATTR) + .map((p) => p.value) .join(' '); attrsParts = !!attrsParts ? ` ${attrsParts}` : ''; @@ -157,26 +180,36 @@ export function linkPieces(pieces: CodePiece[]): string { return `${beforeParts}<${tagName}${attrsParts} />${afterParts}`; } -export function createNodeGenerator(handlers: HandlerSet, plugins: ExtGeneratorPlugin[]) { - const generateNode = (nodeItem: IComponentNodeItem): string => { +export function createNodeGenerator( + handlers: HandlerSet, + plugins: ExtGeneratorPlugin[], + customHandlers: CustomHandlerSet = {}, +) { + const generateNode = (nodeItem: NodeSchema): string => { let pieces: CodePiece[] = []; - plugins.forEach(p => { - pieces = pieces.concat(p(nodeItem)); + plugins.forEach((p) => { + pieces = pieces.concat(p(nodeItem, customHandlers)); }); + pieces = pieces.concat(generateBasicNode(nodeItem)); - pieces = pieces.concat(generateAttrs(nodeItem)); - if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) { - pieces = pieces.concat(handleChildren(nodeItem.children, handlers).map(l => ({ - type: PIECE_TYPE.CHILDREN, - value: l, - }))); + pieces = pieces.concat(generateAttrs(nodeItem, customHandlers)); + + if (nodeItem.children && !_.isEmpty(nodeItem.children)) { + pieces = pieces.concat( + handleChildren(nodeItem.children, handlers).map((l) => ({ + type: PIECE_TYPE.CHILDREN, + value: l, + })), + ); } - return linkPieces(pieces); + return linkPieces(pieces, customHandlers); }; - handlers.node = (input: IComponentNodeItem) => [generateNode(input)]; + handlers.node = (node: NodeSchema) => [generateNode(node)]; + + customHandlers.node = customHandlers.node || generateNode; return generateNode; } @@ -184,10 +217,11 @@ export function createNodeGenerator(handlers: HandlerSet, plugins: ExtGe export const generateString = (input: string) => [input]; export function createReactNodeGenerator() { - return createNodeGenerator({ - string: generateString, - expression: (input) => [generateExpression(input)], - }, [ - generateReactCtrlLine, - ]); + return createNodeGenerator( + { + string: generateString, + expression: (input) => [generateExpression(input)], + }, + [generateReactCtrlLine], + ); } diff --git a/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/pages/Home/index.jsx b/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/pages/Home/index.jsx index 91bef677d..1ae8e9192 100644 --- a/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/pages/Home/index.jsx +++ b/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/pages/Home/index.jsx @@ -6,9 +6,13 @@ import Text from 'rax-text'; import { createDataSourceEngine } from '@ali/lowcode-datasource-engine'; +import __$$projectUtils from '../../utils'; + import './index.css'; class Home$$Page extends Component { + _methods = this._defineMethods(); + _context = this._createContext(); _dataSourceList = this._defineDataSourceList(); @@ -21,6 +25,8 @@ class Home$$Page extends Component { } render() { + const __$$context = this._context; + return ( Hello world! @@ -66,8 +72,35 @@ class Home$$Page extends Component { } _defineUtils() { + const utils = { + ...__$$projectUtils, + }; + + Object.entries(utils).forEach(([name, util]) => { + if (typeof util === 'function') { + utils[name] = util.bind(this._context); + } + }); + + return utils; + } + + _defineMethods() { return {}; } } export default Home$$Page; + +function __$$eval(expr) { + try { + return expr(); + } catch (err) { + console.warn('Failed to evaluate: ', expr, err); + } +} + +function __$$evalArray(expr) { + const res = __$$eval(expr); + return Array.isArray(res) ? res : []; +} diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.editorconfig b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.editorconfig new file mode 100644 index 000000000..5760be583 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.eslintignore b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.eslintignore new file mode 100644 index 000000000..3b437e614 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.eslintignore @@ -0,0 +1,11 @@ +# 忽略目录 +build/ +tests/ +demo/ + +# node 覆盖率文件 +coverage/ + +# 忽略文件 +**/*-min.js +**/*.min.js diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.eslintrc.js b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.eslintrc.js new file mode 100644 index 000000000..e2a7c5b54 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['rax'], +}; diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.gitignore b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.gitignore new file mode 100644 index 000000000..50a53dace --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/.gitignore @@ -0,0 +1,17 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +*~ +*.swp +*.log + +.DS_Store +.idea/ +.temp/ + +build/ +dist/ +lib/ +coverage/ +node_modules/ + +template.yml diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/README.md b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/README.md new file mode 100644 index 000000000..6eff85d41 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/README.md @@ -0,0 +1,15 @@ +# @ali/rax-component-demo + +## Getting Started + +### `npm run start` + +Runs the app in development mode. + +Open [http://localhost:9999](http://localhost:9999) to view it in the browser. + +The page will reload if you make edits. + +### `npm run build` + +Builds the app for production to the `build` folder. diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/abc.json b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/abc.json new file mode 100644 index 000000000..f9ee40f71 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/abc.json @@ -0,0 +1,7 @@ +{ + "type": "rax", + "builder": "@ali/builder-rax-v1", + "info": { + "raxVersion": "1.x" + } +} diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/build.json b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/build.json new file mode 100644 index 000000000..f3e9b9323 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/build.json @@ -0,0 +1,12 @@ +{ + "inlineStyle": false, + "plugins": [ + [ + "build-plugin-rax-app", + { + "targets": ["web", "miniapp"] + } + ], + "@ali/build-plugin-rax-app-def" + ] +} diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/package.json b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/package.json new file mode 100644 index 000000000..8aad3e1ad --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/package.json @@ -0,0 +1,35 @@ +{ + "name": "@ali/rax-app-demo", + "private": true, + "version": "1.0.0", + "scripts": { + "build": "rm -f ./dist/miniapp.tar.gz && npm run build:miniapp && cd build/miniapp && tar czf ../../dist/miniapp.tar.gz *", + "build:miniapp": "build-scripts build", + "start": "build-scripts start", + "lint": "eslint --ext .js --ext .jsx ./" + }, + "dependencies": { + "@ali/lowcode-datasource-engine": "^0.1.0", + "rax": "^1.1.0", + "rax-app": "^2.0.0", + "rax-document": "^0.1.0", + "rax-view": "^1.0.0", + "rax-text": "^1.0.0", + "rax-image": "^1.0.0", + "moment": "*", + "lodash": "*" + }, + "devDependencies": { + "build-plugin-rax-app": "^5.0.0", + "@alib/build-scripts": "^0.1.0", + "@typescript-eslint/eslint-plugin": "^2.11.0", + "@typescript-eslint/parser": "^2.11.0", + "babel-eslint": "^10.0.3", + "eslint": "^6.8.0", + "eslint-config-rax": "^0.1.0", + "eslint-plugin-import": "^2.20.0", + "eslint-plugin-module": "^0.1.0", + "eslint-plugin-react": "^7.18.0", + "@ali/build-plugin-rax-app-def": "^1.0.0" + } +} diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/app.js b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/app.js new file mode 100644 index 000000000..f04a4233b --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/app.js @@ -0,0 +1,4 @@ +import { runApp } from 'rax-app'; +import appConfig from './app.json'; + +runApp(appConfig); diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/app.json b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/app.json new file mode 100644 index 000000000..63dec4d7d --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/app.json @@ -0,0 +1,11 @@ +{ + "routes": [ + { + "path": "/", + "source": "pages/Home/index" + } + ], + "window": { + "title": "Rax App Demo" + } +} diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/document/index.jsx b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/document/index.jsx new file mode 100644 index 000000000..50569a9b6 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/document/index.jsx @@ -0,0 +1,25 @@ +import { createElement } from 'rax'; +import { Root, Style, Script } from 'rax-document'; + +function Document() { + return ( + + + + + Rax App Demo +