import { ResultDir, ResultFile, ProjectSchema } from '@ali/lowcode-types'; import { IModuleBuilder, IParseResult, IProjectBuilder, IProjectPlugins, IProjectTemplate, ISchemaParser, PostProcessor, } from '../types'; import SchemaParser from '../parser/SchemaParser'; import { createResultDir, addDirectory, addFile } from '../utils/resultHelper'; import { createModuleBuilder } from '../generator/ModuleBuilder'; interface IModuleInfo { moduleName?: string; path: string[]; files: ResultFile[]; } function getDirFromRoot(root: ResultDir, path: string[]): ResultDir { let current: ResultDir = root; path.forEach((p) => { const exist = current.dirs.find((d) => d.name === p); if (exist) { current = exist; } else { const newDir = createResultDir(p); addDirectory(current, newDir); current = newDir; } }); return current; } export class ProjectBuilder implements IProjectBuilder { private template: IProjectTemplate; private plugins: IProjectPlugins; private postProcessors: PostProcessor[]; constructor({ template, plugins, postProcessors, }: { template: IProjectTemplate; plugins: IProjectPlugins; postProcessors: PostProcessor[]; }) { this.template = template; this.plugins = plugins; this.postProcessors = postProcessors; } async generateProject(schema: ProjectSchema | string): Promise { // Init const schemaParser: ISchemaParser = new SchemaParser(); const builders = this.createModuleBuilders(); const projectRoot = this.template.generateTemplate(); // Validate // Parse / Format // Preprocess // Collect Deps // Parse JSExpression const parseResult: IParseResult = schemaParser.parse(schema); let buildResult: IModuleInfo[] = []; // Generator Code module // components // pages const containerBuildResult: IModuleInfo[] = await Promise.all( parseResult.containers.map(async (containerInfo) => { let builder: IModuleBuilder; let path: string[]; if (containerInfo.containerType === 'Page') { builder = builders.pages; path = this.template.slots.pages.path; } else { builder = builders.components; path = this.template.slots.components.path; } const { files } = await builder.generateModule(containerInfo); return { moduleName: containerInfo.moduleName, path, files, }; }), ); buildResult = buildResult.concat(containerBuildResult); // router if (parseResult.globalRouter && builders.router) { const { files } = await builders.router.generateModule(parseResult.globalRouter); buildResult.push({ path: this.template.slots.router.path, files, }); } // entry if (parseResult.project && builders.entry) { const { files } = await builders.entry.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.entry.path, files, }); } // appConfig if (builders.appConfig) { const { files } = await builders.appConfig.generateModule(parseResult); buildResult.push({ path: this.template.slots.appConfig.path, files, }); } // buildConfig if (builders.buildConfig) { const { files } = await builders.buildConfig.generateModule(parseResult); buildResult.push({ path: this.template.slots.buildConfig.path, files, }); } // constants? if (parseResult.project && builders.constants && this.template.slots.constants) { const { files } = await builders.constants.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.constants.path, files, }); } // utils? if (parseResult.globalUtils && builders.utils && this.template.slots.utils) { const { files } = await builders.utils.generateModule(parseResult.globalUtils); buildResult.push({ path: this.template.slots.utils.path, files, }); } // i18n? if (builders.i18n && this.template.slots.i18n) { const { files } = await builders.i18n.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.i18n.path, files, }); } // globalStyle if (parseResult.project && builders.globalStyle) { const { files } = await builders.globalStyle.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.globalStyle.path, files, }); } // htmlEntry if (parseResult.project && builders.htmlEntry) { const { files } = await builders.htmlEntry.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.htmlEntry.path, files, }); } // packageJSON if (parseResult.project && builders.packageJSON) { const { files } = await builders.packageJSON.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.packageJSON.path, files, }); } // TODO: 更多 slots 的处理??是不是可以考虑把 template 中所有的 slots 都处理下? // Post Process // Combine Modules buildResult.forEach((moduleInfo) => { let targetDir = getDirFromRoot(projectRoot, moduleInfo.path); if (moduleInfo.moduleName) { const dir = createResultDir(moduleInfo.moduleName); addDirectory(targetDir, dir); targetDir = dir; } moduleInfo.files.forEach((file) => addFile(targetDir, file)); }); return projectRoot; } private createModuleBuilders(): Record { const builders: Record = {}; Object.keys(this.plugins).forEach((pluginName) => { if (this.plugins[pluginName].length > 0) { const options: { mainFileName?: string } = {}; if (this.template.slots[pluginName] && this.template.slots[pluginName].fileName) { options.mainFileName = this.template.slots[pluginName].fileName; } builders[pluginName] = createModuleBuilder({ plugins: this.plugins[pluginName], postProcessors: this.postProcessors, ...options, }); } }); return builders; } } export function createProjectBuilder({ template, plugins, postProcessors, }: { template: IProjectTemplate; plugins: IProjectPlugins; postProcessors: PostProcessor[]; }): IProjectBuilder { return new ProjectBuilder({ template, plugins, postProcessors, }); }