diff --git a/.eslintignore b/.eslintignore index eaf713eb7..7b0cf9688 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,7 @@ # 忽略目录 node_modules +test-cases +output build dist demo diff --git a/.vscode/launch.json b/.vscode/launch.json index 98523af22..68ac35b55 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,16 @@ "type": "node", "request": "launch", "runtimeExecutable": "${workspaceFolder}/packages/material-parser/node_modules/.bin/ava", - "runtimeArgs": ["debug", "--break", "${workspaceFolder}/packages/material-parser/test/antd.ts"] + "runtimeArgs": ["debug", "--break", "${file}"] + }, + { + "name": "ava code-gen", + "type": "node", + "request": "launch", + "runtimeExecutable": "node", + "runtimeArgs": ["${workspaceFolder}/node_modules/.bin/ava", "--serial", "${file}"], + "cwd": "${workspaceFolder}/packages/code-generator", + "outputCapture": "std" } ] } diff --git a/packages/code-generator/.eslintignore b/packages/code-generator/.eslintignore new file mode 100644 index 000000000..bcfd4b921 --- /dev/null +++ b/packages/code-generator/.eslintignore @@ -0,0 +1 @@ +test-cases/ diff --git a/packages/code-generator/README.md b/packages/code-generator/README.md index fe71e6c6a..35b2d9aba 100644 --- a/packages/code-generator/README.md +++ b/packages/code-generator/README.md @@ -1,9 +1,31 @@ # 出码模块 +详细介绍看这里: + ## 安装接入 - ## 自定义导出 - ## 开始开发 + +本项目隶属于 [ali-lowcode-engine](http://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine), 需要和整个 [ali-lowcode-engine](http://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine) 一起开发。 + +所以先要初始化整个 [ali-lowcode-engine](http://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine) 的环境: + +1. 克隆 [ali-lowcode-engine](http://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine): `git clone git@gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine.git` +2. 运行 `setup` 脚本,初始化环境: `npm run setup` + +然后,因为本项目依赖 `@ali/lowcode-types` 所以需要先构建下 `type`,即执行: `lerna run build --scope @ali/lowcode-types` + +最后,可以运行 `npm start` 命令启动本地调试(本项目通过 `ava` 进行单元测试,故 `start` 脚本其实就是 `watch` 模式的 `ava`): + +```sh +# 到本项目目录下执行:(推荐) +npm start + +# 或直接执行 ava: +npx ava --watch + +# 或在 ali-lowcode-engine 工程根目录下执行: (不推荐,因为命令太长而且没法响应输入) +lerna run start --stream --scope @ali/lowcode-code-generator +``` diff --git a/packages/code-generator/build.json b/packages/code-generator/build.json new file mode 100644 index 000000000..bd5cf18dd --- /dev/null +++ b/packages/code-generator/build.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "build-plugin-component" + ] +} diff --git a/packages/code-generator/demo/demo.js b/packages/code-generator/demo/demo.js index 1c760446d..2b6e02f52 100644 --- a/packages/code-generator/demo/demo.js +++ b/packages/code-generator/demo/demo.js @@ -59,7 +59,9 @@ function main() { builder.generateProject(schemaJson).then((result) => { displayResultInConsole(result); - writeResultToDisk(result, 'output/lowcodeDemo').then((response) => console.log('Write to disk: ', JSON.stringify(response))); + writeResultToDisk(result, 'output/lowcodeDemo').then((response) => + console.log('Write to disk: ', JSON.stringify(response)), + ); return result; }); } @@ -160,12 +162,14 @@ function exportProject() { builder.generateProject(schemaJson).then((result) => { displayResultInConsole(result); - writeResultToDisk(result, 'output/lowcodeDemo').then((response) => console.log('Write to disk: ', JSON.stringify(response))); + writeResultToDisk(result, 'output/lowcodeDemo').then((response) => + console.log('Write to disk: ', JSON.stringify(response)), + ); return result; }); } // main(); // exportModule(); -// exportProject(); -demo(); +exportProject(); +// demo(); diff --git a/packages/code-generator/package.json b/packages/code-generator/package.json index 3d9840d6a..dd87e5f0a 100644 --- a/packages/code-generator/package.json +++ b/packages/code-generator/package.json @@ -3,18 +3,23 @@ "version": "1.0.8-0", "description": "出码引擎 for LowCode Engine", "main": "lib/index.js", + "module": "es/index.js", + "typings": "es/index.d.ts", "files": [ "lib", + "es", "demo" ], "scripts": { - "build": "rimraf lib && tsc", + "start": "ava --watch", + "build": "rimraf lib && build-scripts build --skip-demo", "demo": "node ./demo/demo.js", "test": "ava", "template": "node ./tools/createTemplate.js" }, "dependencies": { "@ali/am-eslint-config": "*", + "@ali/lowcode-types": "^0.8.14", "@ali/my-prettier": "^1.0.0", "@babel/generator": "^7.9.5", "@babel/parser": "^7.9.4", @@ -24,24 +29,34 @@ "change-case": "^3.1.0", "jszip": "^3.5.0", "prettier": "^2.0.2", + "semver": "^7.3.2", "short-uuid": "^3.1.1" }, "devDependencies": { + "@alib/build-scripts": "^0.1.18", "@types/babel__traverse": "^7.0.10", "ava": "^1.0.1", "rimraf": "^3.0.2", "ts-loader": "^6.2.2", - "ts-node": "^7.0.1", + "ts-node": "^8.10.2", "tsconfig-paths": "^3.9.0" }, "ava": { "compileEnhancements": false, "snapshotDir": "test/fixtures/__snapshots__", + "files": [ + "test/**/*.test.ts" + ], "extensions": [ "ts" ], "require": [ - "ts-node/register" + "ts-node/register/transpile-only" + ], + "sources": [ + "src/**/*", + "test/**/*", + "test-cases/**/expected/**/*" ] }, "publishConfig": { diff --git a/packages/code-generator/src/const/generator.ts b/packages/code-generator/src/const/generator.ts index 3d3950420..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', @@ -21,24 +22,29 @@ export const CLASS_DEFINE_CHUNK_NAME = { InsVar: 'CommonClassDefineInsVar', InsVarMethod: 'CommonClassDefineInsVarMethod', InsMethod: 'CommonClassDefineInsMethod', + InsPrivateMethod: 'CommonClassDefineInsPrivateMethod', End: 'CommonClassDefineEnd', }; 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, ], @@ -49,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, @@ -82,7 +81,7 @@ export const DEFAULT_LINK_AFTER = { CLASS_DEFINE_CHUNK_NAME.InsVarMethod, CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, ], - [CLASS_DEFINE_CHUNK_NAME.End]: [ + [CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod]: [ CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar, CLASS_DEFINE_CHUNK_NAME.StaticMethod, @@ -91,9 +90,20 @@ export const DEFAULT_LINK_AFTER = { CLASS_DEFINE_CHUNK_NAME.InsMethod, CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, ], + [CLASS_DEFINE_CHUNK_NAME.End]: [ + CLASS_DEFINE_CHUNK_NAME.Start, + CLASS_DEFINE_CHUNK_NAME.StaticVar, + CLASS_DEFINE_CHUNK_NAME.StaticMethod, + CLASS_DEFINE_CHUNK_NAME.InsVar, + CLASS_DEFINE_CHUNK_NAME.InsVarMethod, + CLASS_DEFINE_CHUNK_NAME.InsMethod, + CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, + ], [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, @@ -101,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, diff --git a/packages/code-generator/src/generator/ChunkBuilder.ts b/packages/code-generator/src/generator/ChunkBuilder.ts index 67ecd504e..d9dc8e3c6 100644 --- a/packages/code-generator/src/generator/ChunkBuilder.ts +++ b/packages/code-generator/src/generator/ChunkBuilder.ts @@ -21,6 +21,7 @@ export const groupChunks = (chunks: ICodeChunk[]): ICodeChunk[][] => { const col = chunks.reduce((chunksSet: Record, chunk) => { const fileKey = chunk.subModule || COMMON_SUB_MODULE_NAME; if (!chunksSet[fileKey]) { + // eslint-disable-next-line no-param-reassign chunksSet[fileKey] = []; } const res = whichFamily(chunk.fileType as FileType); @@ -56,6 +57,7 @@ export const groupChunks = (chunks: ICodeChunk[]): ICodeChunk[][] => { let t: string = info.chunk.fileType; if (info.familyIdx !== undefined) { t = FILE_TYPE_FAMILY[info.familyIdx][tmp[key][info.familyIdx]]; + // eslint-disable-next-line no-param-reassign info.chunk.fileType = t; } if (!byType[t]) { diff --git a/packages/code-generator/src/generator/CodeBuilder.ts b/packages/code-generator/src/generator/CodeBuilder.ts index 21b471f50..9a850d4a4 100644 --- a/packages/code-generator/src/generator/CodeBuilder.ts +++ b/packages/code-generator/src/generator/CodeBuilder.ts @@ -1,18 +1,13 @@ -import { - ChunkContent, - ChunkType, - CodeGeneratorError, - CodeGeneratorFunction, - ICodeBuilder, - ICodeChunk, -} from '../types'; +import { ChunkContent, ChunkType, CodeGeneratorError, CodeGeneratorFunction, ICodeBuilder, ICodeChunk } from '../types'; export default class Builder implements ICodeBuilder { private chunkDefinitions: ICodeChunk[] = []; private generators: { [key: string]: CodeGeneratorFunction } = { - [ChunkType.STRING]: (str: string) => str, // no-op for string chunks - [ChunkType.JSON]: (json: object) => JSON.stringify(json), // stringify json to string + // no-op for string chunks + [ChunkType.STRING]: (str: string) => str, + // stringify json to string + [ChunkType.JSON]: (json: Record) => JSON.stringify(json), }; constructor(chunkDefinitions: ICodeChunk[] = []) { @@ -23,13 +18,13 @@ export default class Builder implements ICodeBuilder { * Links all chunks together based on their requirements. Returns an array * of ordered chunk names which need to be compiled and glued together. */ - public link(chunkDefinitions: ICodeChunk[] = []): string { + link(chunkDefinitions: ICodeChunk[] = []): string { const chunks = chunkDefinitions || this.chunkDefinitions; if (chunks.length <= 0) { return ''; } - const unprocessedChunks = chunks.map(chunk => { + const unprocessedChunks = chunks.map((chunk) => { return { name: chunk.name, type: chunk.type, @@ -50,9 +45,7 @@ export default class Builder implements ICodeBuilder { } if (unprocessedChunks[indexToRemove].linkAfter.length > 0) { - throw new CodeGeneratorError( - 'Operation aborted. Reason: cyclic dependency between chunks.', - ); + throw new CodeGeneratorError('Operation aborted. Reason: cyclic dependency between chunks.'); } const { type, content, name } = unprocessedChunks[indexToRemove]; @@ -62,10 +55,13 @@ export default class Builder implements ICodeBuilder { } unprocessedChunks.splice(indexToRemove, 1); - if (!unprocessedChunks.some(ch => ch.name === name)) { + if (!unprocessedChunks.some((ch) => ch.name === name)) { unprocessedChunks.forEach( // remove the processed chunk from all the linkAfter arrays from the remaining chunks - ch => (ch.linkAfter = ch.linkAfter.filter(after => after !== name)), + (ch) => { + // eslint-disable-next-line no-param-reassign + ch.linkAfter = ch.linkAfter.filter((after) => after !== name); + }, ); } } @@ -73,14 +69,12 @@ export default class Builder implements ICodeBuilder { return resultingString.join('\n'); } - public generateByType(type: string, content: unknown): string { + generateByType(type: string, content: unknown): string { if (!content) { return ''; } if (Array.isArray(content)) { - return content - .map(contentItem => this.generateByType(type, contentItem)) - .join('\n'); + return content.map((contentItem) => this.generateByType(type, contentItem)).join('\n'); } if (!this.generators[type]) { @@ -95,6 +89,6 @@ export default class Builder implements ICodeBuilder { // remove invalid chunks (which did not end up being created) from the linkAfter fields // one use-case is when you want to remove the import plugin private cleanupInvalidChunks(linkAfter: string[], chunks: ICodeChunk[]) { - return linkAfter.filter(chunkName => chunks.some(chunk => chunk.name === chunkName)); + return linkAfter.filter((chunkName) => chunks.some((chunk) => chunk.name === chunkName)); } } diff --git a/packages/code-generator/src/generator/ModuleBuilder.ts b/packages/code-generator/src/generator/ModuleBuilder.ts index f70abc669..e31fdc58b 100644 --- a/packages/code-generator/src/generator/ModuleBuilder.ts +++ b/packages/code-generator/src/generator/ModuleBuilder.ts @@ -1,13 +1,12 @@ +import { ProjectSchema, ResultFile, ResultDir } from '@ali/lowcode-types'; + import { BuilderComponentPlugin, CodeGeneratorError, - IBasicSchema, ICodeChunk, ICompiledModule, IModuleBuilder, IParseResult, - IResultDir, - IResultFile, ISchemaParser, PostProcessor, } from '../types'; @@ -17,9 +16,7 @@ import { COMMON_SUB_MODULE_NAME } from '../const/generator'; import SchemaParser from '../parser/SchemaParser'; import ChunkBuilder from './ChunkBuilder'; import CodeBuilder from './CodeBuilder'; - -import ResultDir from '../model/ResultDir'; -import ResultFile from '../model/ResultFile'; +import { createResultFile, createResultDir, addFile } from '../utils/resultHelper'; export function createModuleBuilder( options: { @@ -40,24 +37,24 @@ export function createModuleBuilder( throw new CodeGeneratorError('No plugins found. Component generation cannot work without any plugins!'); } - let files: IResultFile[] = []; + let files: ResultFile[] = []; const { chunks } = await chunkGenerator.run(input); chunks.forEach((fileChunkList) => { const content = linker.link(fileChunkList); - const file = new ResultFile(fileChunkList[0].subModule || moduleMainName, fileChunkList[0].fileType, content); + const file = createResultFile(fileChunkList[0].subModule || moduleMainName, fileChunkList[0].fileType, content); files.push(file); }); if (options.postProcessors.length > 0) { files = files.map((file) => { - let { content } = file; + let content = file.content; const type = file.ext; options.postProcessors.forEach((processer) => { content = processer(content, type); }); - return new ResultFile(file.name, type, content); + return createResultFile(file.name, type, content); }); } @@ -66,7 +63,7 @@ export function createModuleBuilder( }; }; - const generateModuleCode = async (schema: IBasicSchema | string): Promise => { + const generateModuleCode = async (schema: ProjectSchema | string): Promise => { // Init const schemaParser: ISchemaParser = new SchemaParser(); const parseResult: IParseResult = schemaParser.parse(schema); @@ -74,19 +71,19 @@ export function createModuleBuilder( const containerInfo = parseResult.containers[0]; const { files } = await generateModule(containerInfo); - const dir = new ResultDir(containerInfo.moduleName); - files.forEach((file) => dir.addFile(file)); + const dir = createResultDir(containerInfo.moduleName); + files.forEach((file) => addFile(dir, file)); return dir; }; const linkCodeChunks = (chunks: Record, fileName: string) => { - const files: IResultFile[] = []; + const files: ResultFile[] = []; Object.keys(chunks).forEach((fileKey) => { const fileChunkList = chunks[fileKey]; const content = linker.link(fileChunkList); - const file = new ResultFile(fileChunkList[0].subModule || fileName, fileChunkList[0].fileType, content); + const file = createResultFile(fileChunkList[0].subModule || fileName, fileChunkList[0].fileType, content); files.push(file); }); diff --git a/packages/code-generator/src/generator/ProjectBuilder.ts b/packages/code-generator/src/generator/ProjectBuilder.ts index a10ad67e8..5486cd538 100644 --- a/packages/code-generator/src/generator/ProjectBuilder.ts +++ b/packages/code-generator/src/generator/ProjectBuilder.ts @@ -1,36 +1,35 @@ +import { ResultDir, ResultFile, ProjectSchema } from '@ali/lowcode-types'; + import { IModuleBuilder, IParseResult, IProjectBuilder, IProjectPlugins, - IProjectSchema, IProjectTemplate, - IResultDir, - IResultFile, ISchemaParser, PostProcessor, } from '../types'; -import ResultDir from '../model/ResultDir'; import SchemaParser from '../parser/SchemaParser'; +import { createResultDir, addDirectory, addFile } from '../utils/resultHelper'; import { createModuleBuilder } from '../generator/ModuleBuilder'; interface IModuleInfo { moduleName?: string; path: string[]; - files: IResultFile[]; + files: ResultFile[]; } -function getDirFromRoot(root: IResultDir, path: string[]): IResultDir { - let current: IResultDir = root; +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 = new ResultDir(p); - current.addDirectory(newDir); + const newDir = createResultDir(p); + addDirectory(current, newDir); current = newDir; } }); @@ -59,7 +58,7 @@ export class ProjectBuilder implements IProjectBuilder { this.postProcessors = postProcessors; } - async generateProject(schema: IProjectSchema | string): Promise { + async generateProject(schema: ProjectSchema | string): Promise { // Init const schemaParser: ISchemaParser = new SchemaParser(); const builders = this.createModuleBuilders(); @@ -119,6 +118,27 @@ export class ProjectBuilder implements IProjectBuilder { 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); @@ -128,6 +148,7 @@ export class ProjectBuilder implements IProjectBuilder { files, }); } + // utils? if (parseResult.globalUtils && builders.utils && this.template.slots.utils) { const { files } = await builders.utils.generateModule(parseResult.globalUtils); @@ -137,15 +158,17 @@ export class ProjectBuilder implements IProjectBuilder { files, }); } + // i18n? - if (parseResult.globalI18n && builders.i18n && this.template.slots.i18n) { - const { files } = await builders.i18n.generateModule(parseResult.globalI18n); + 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); @@ -155,6 +178,7 @@ export class ProjectBuilder implements IProjectBuilder { files, }); } + // htmlEntry if (parseResult.project && builders.htmlEntry) { const { files } = await builders.htmlEntry.generateModule(parseResult.project); @@ -164,6 +188,7 @@ export class ProjectBuilder implements IProjectBuilder { files, }); } + // packageJSON if (parseResult.project && builders.packageJSON) { const { files } = await builders.packageJSON.generateModule(parseResult.project); @@ -174,17 +199,19 @@ export class ProjectBuilder implements IProjectBuilder { }); } + // TODO: 更多 slots 的处理??是不是可以考虑把 template 中所有的 slots 都处理下? + // Post Process // Combine Modules buildResult.forEach((moduleInfo) => { let targetDir = getDirFromRoot(projectRoot, moduleInfo.path); if (moduleInfo.moduleName) { - const dir = new ResultDir(moduleInfo.moduleName); - targetDir.addDirectory(dir); + const dir = createResultDir(moduleInfo.moduleName); + addDirectory(targetDir, dir); targetDir = dir; } - moduleInfo.files.forEach((file) => targetDir.addFile(file)); + moduleInfo.files.forEach((file) => addFile(targetDir, file)); }); return projectRoot; diff --git a/packages/code-generator/src/index.ts b/packages/code-generator/src/index.ts index 79e527354..3d368be5a 100644 --- a/packages/code-generator/src/index.ts +++ b/packages/code-generator/src/index.ts @@ -7,7 +7,8 @@ import { createModuleBuilder } from './generator/ModuleBuilder'; import { createDiskPublisher } from './publisher/disk'; import { createZipPublisher } from './publisher/zip'; import createIceJsProjectBuilder from './solutions/icejs'; -import createRecoreProjectBuilder from './solutions/recore'; +// import createRecoreProjectBuilder from './solutions/recore'; +import createRaxAppProjectBuilder from './solutions/rax-app'; // 引入说明 import { REACT_CHUNK_NAME } from './plugins/component/react/const'; @@ -34,11 +35,15 @@ import prettier from './postprocessor/prettier'; import * as utilsCommon from './utils/common'; import * as utilsCompositeType from './utils/compositeType'; import * as utilsJsExpression from './utils/jsExpression'; +import * as utilsJsSlot from './utils/jsSlot'; import * as utilsNodeToJSX from './utils/nodeToJSX'; +import * as utilsResultHelper from './utils/resultHelper'; import * as utilsTemplateHelper from './utils/templateHelper'; +import * as utilsValidate from './utils/validate'; // 引入内置解决方案模块 import icejs from './plugins/project/framework/icejs'; +import rax from './plugins/project/framework/rax'; export * from './types'; @@ -47,10 +52,12 @@ export default { createModuleBuilder, solutions: { icejs: createIceJsProjectBuilder, - recore: createRecoreProjectBuilder, + // recore: createRecoreProjectBuilder, + rax: createRaxAppProjectBuilder, }, solutionParts: { icejs, + rax, }, publishers: { disk: createDiskPublisher, @@ -87,8 +94,11 @@ export default { common: utilsCommon, compositeType: utilsCompositeType, jsExpression: utilsJsExpression, + jsSlot: utilsJsSlot, nodeToJSX: utilsNodeToJSX, + resultHelper: utilsResultHelper, templateHelper: utilsTemplateHelper, + validate: utilsValidate, }, chunkNames: { COMMON_CHUNK_NAME, diff --git a/packages/code-generator/src/model/ResultDir.ts b/packages/code-generator/src/model/ResultDir.ts deleted file mode 100644 index c456a9459..000000000 --- a/packages/code-generator/src/model/ResultDir.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CodeGeneratorError, IResultDir, IResultFile } from '../types'; - -export default class ResultDir implements IResultDir { - public name: string; - - public dirs: IResultDir[]; - - public files: IResultFile[]; - - constructor(name: string) { - this.name = name; - this.dirs = []; - this.files = []; - } - - public addDirectory(dir: IResultDir): void { - if (this.dirs.findIndex(d => d.name === dir.name) < 0) { - this.dirs.push(dir); - } else { - throw new CodeGeneratorError('Adding same directory to one directory'); - } - } - - public addFile(file: IResultFile): void { - if ( - this.files.findIndex(f => f.name === file.name && f.ext === file.ext) < 0 - ) { - this.files.push(file); - } else { - throw new CodeGeneratorError('Adding same file to one directory'); - } - } -} diff --git a/packages/code-generator/src/model/ResultFile.ts b/packages/code-generator/src/model/ResultFile.ts deleted file mode 100644 index b8064bfab..000000000 --- a/packages/code-generator/src/model/ResultFile.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IResultFile } from '../types'; - -export default class ResultFile implements IResultFile { - public name: string; - - public ext: string; - - public content: string; - - constructor(name: string, ext = 'jsx', content = '') { - this.name = name; - this.ext = ext; - this.content = content; - } -} diff --git a/packages/code-generator/src/parser/SchemaParser.ts b/packages/code-generator/src/parser/SchemaParser.ts index 61ca7c806..b05df3ec5 100644 --- a/packages/code-generator/src/parser/SchemaParser.ts +++ b/packages/code-generator/src/parser/SchemaParser.ts @@ -2,30 +2,28 @@ * 解析器是对输入的固定格式数据做拆解,使其符合引擎后续步骤预期,完成统一处理逻辑的步骤。 * 本解析器面向的是标准 schema 协议。 */ +import changeCase from 'change-case'; +import { UtilItem, NodeDataType, NodeSchema, ContainerSchema, ProjectSchema, PropsMap } from '@ali/lowcode-types'; import { SUPPORT_SCHEMA_VERSION_LIST } from '../const'; -import { handleSubNodes } from '../utils/nodeToJSX'; +import { handleSubNodes } from '../utils/schema'; import { uniqueArray } from '../utils/common'; import { - ChildNodeType, CodeGeneratorError, CompatibilityError, DependencyType, - IBasicSchema, - IComponentNodeItem, IContainerInfo, - IContainerNodeItem, + IDependency, IExternalDependency, IInternalDependency, InternalDependencyType, - IPageMeta, IParseResult, - IProjectSchema, ISchemaParser, - IUtilItem, INpmPackage, + IRouterInfo, + IPageMeta, } from '../types'; const defaultContainer: IContainerInfo = { @@ -38,7 +36,7 @@ const defaultContainer: IContainerInfo = { }; class SchemaParser implements ISchemaParser { - validate(schema: IBasicSchema): boolean { + validate(schema: ProjectSchema): boolean { if (SUPPORT_SCHEMA_VERSION_LIST.indexOf(schema.version) < 0) { throw new CompatibilityError(`Not support schema with version [${schema.version}]`); } @@ -46,13 +44,13 @@ class SchemaParser implements ISchemaParser { return true; } - parse(schemaSrc: IProjectSchema | string): IParseResult { + parse(schemaSrc: ProjectSchema | string): IParseResult { // TODO: collect utils depends in JSExpression const compDeps: Record = {}; const internalDeps: Record = {}; let utilsDeps: IExternalDependency[] = []; - let schema: IProjectSchema; + let schema: ProjectSchema; if (typeof schemaSrc === 'string') { try { schema = JSON.parse(schemaSrc); @@ -65,31 +63,38 @@ class SchemaParser implements ISchemaParser { // 解析三方组件依赖 schema.componentsMap.forEach((info) => { - info.dependencyType = DependencyType.External; - info.importName = info.componentName; - compDeps[info.componentName] = info; + if (info.componentName) { + compDeps[info.componentName] = { + ...info, + dependencyType: DependencyType.External, + componentName: 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: ContainerSchema = schema.componentsTree[0] as ContainerSchema; - if (!firstRoot.fileName) { + if (!('fileName' in firstRoot) || !firstRoot.fileName) { // 整个 schema 描述一个容器,且无根节点定义 const container: IContainerInfo = { ...defaultContainer, - children: schema.componentsTree as IComponentNodeItem[], + children: schema.componentsTree as NodeSchema[], }; containers = [container]; } else { // 普通带 1 到多个容器的 schema containers = schema.componentsTree.map((n) => { - const subRoot = n as IContainerNodeItem; + const subRoot = n as ContainerSchema; const container: IContainerInfo = { ...subRoot, containerType: subRoot.componentName, - moduleName: subRoot.fileName, // TODO: 驼峰化名称 + moduleName: changeCase.pascalCase(subRoot.fileName), }; return container; }); @@ -124,6 +129,7 @@ class SchemaParser implements ISchemaParser { internalDeps[dep.moduleName] = dep; }); + const containersDeps = ([] as IDependency[]).concat(...containers.map((c) => c.deps || [])); // TODO: 不应该在出码部分解决? // 处理 children 写在了 props 里的情况 containers.forEach((container) => { @@ -131,11 +137,18 @@ class SchemaParser implements ISchemaParser { handleSubNodes( container.children, { - node: (i: IComponentNodeItem) => { - if (i.props && i.props.children && !i.children) { - i.children = i.props.children as ChildNodeType; + node: (i: NodeSchema) => { + if (i.props) { + if (Array.isArray(i.props)) { + // FIXME: is array type props description + } else { + const nodeProps = i.props as PropsMap; + if (nodeProps.children && !i.children) { + i.children = nodeProps.children as NodeDataType; + } + } } - return ['']; + return ''; }, }, { @@ -157,18 +170,21 @@ class SchemaParser implements ISchemaParser { }); // 分析路由配置 - const routes = containers + const routes: IRouterInfo['routes'] = containers .filter((container) => container.containerType === 'Page') .map((page) => { - const meta = page.meta as IPageMeta; + const meta = page.meta; if (meta) { return { - path: meta.router, + path: (meta as IPageMeta).router || `/${page.fileName}`, // 如果无法找到页面路由信息,则用 fileName 做兜底 + fileName: page.fileName, componentName: page.moduleName, }; } + return { path: '', + fileName: page.fileName, componentName: page.moduleName, }; }); @@ -178,7 +194,7 @@ class SchemaParser implements ISchemaParser { .filter((dep) => !!dep); // 分析 Utils 依赖 - let utils: IUtilItem[]; + let utils: UtilItem[]; if (schema.utils) { utils = schema.utils; utilsDeps = schema.utils.filter((u) => u.type !== 'function').map((u) => u.content as IExternalDependency); @@ -190,7 +206,7 @@ class SchemaParser implements ISchemaParser { let npms: INpmPackage[] = []; containers.forEach((con) => { const p = (con.deps || []) - .map((dep) => (dep.dependencyType === DependencyType.External ? dep : null)) + .map((dep) => { return (dep.dependencyType === DependencyType.External) ? dep : null; }) .filter((dep) => dep !== null); npms.push(...((p as unknown) as INpmPackage[])); }); @@ -208,21 +224,21 @@ class SchemaParser implements ISchemaParser { deps: routerDeps, }, project: { - meta: schema.meta, - config: schema.config, css: schema.css, constants: schema.constants, i18n: schema.i18n, + containersDeps, + utilsDeps, packages: npms, }, }; } - getComponentNames(children: ChildNodeType): string[] { + getComponentNames(children: NodeDataType): string[] { return handleSubNodes( children, { - node: (i: IComponentNodeItem) => [i.componentName], + node: (i: NodeSchema) => i.componentName, }, { rerun: true, diff --git a/packages/code-generator/src/plugins/common/esmodule.ts b/packages/code-generator/src/plugins/common/esmodule.ts index 2fbff2fed..c8a5bb8c6 100644 --- a/packages/code-generator/src/plugins/common/esmodule.ts +++ b/packages/code-generator/src/plugins/common/esmodule.ts @@ -50,7 +50,12 @@ function groupDepsByPack(deps: IDependency[]): Record { return depMap; } -function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: string): ICodeChunk[] { +function buildPackageImport( + pkg: string, + deps: IDependency[], + targetFileType: string, + useAliasName: boolean, +): ICodeChunk[] { const chunks: ICodeChunk[] = []; let defaultImport = ''; let defaultImportAs = ''; @@ -58,8 +63,9 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st deps.forEach((dep) => { const srcName = dep.exportName; - let targetName = dep.importName || dep.exportName; + let targetName = dep.componentName || dep.exportName; + // 如果是自组件,则导出父组件,并且根据自组件命名规则,判断是否需要定义标识符 if (dep.subName) { if (targetName !== `${srcName}.${dep.subName}`) { if (!isValidIdentifier(targetName)) { @@ -69,9 +75,14 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st chunks.push({ type: ChunkType.STRING, fileType: targetFileType, - name: COMMON_CHUNK_NAME.FileVarDefine, + name: COMMON_CHUNK_NAME.ImportAliasDefine, content: `const ${targetName} = ${srcName}.${dep.subName};`, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], + ext: { + originalName: `${srcName}.${dep.subName}`, + aliasName: targetName, + dependency: dep, + }, }); } @@ -86,13 +97,28 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st defaultImport = srcName; defaultImportAs = targetName; } + + if (targetName !== srcName) { + chunks.push({ + type: ChunkType.STRING, + fileType: targetFileType, + name: COMMON_CHUNK_NAME.ImportAliasDefine, + content: '', + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], + ext: { + originalName: srcName, + aliasName: targetName, + dependency: dep, + }, + }); + } }); - const items = Object.keys(imports).map((src) => (src === imports[src] ? src : `${src} as ${imports[src]}`)); + const items = Object.keys(imports).map((src) => { return src === imports[src] || !useAliasName ? src : `${src} as ${imports[src]}`; }); const statementL = ['import']; if (defaultImport) { - statementL.push(defaultImportAs); + statementL.push(useAliasName ? defaultImportAs : defaultImport); if (items.length > 0) { statementL.push(','); } @@ -127,13 +153,15 @@ function buildPackageImport(pkg: string, deps: IDependency[], targetFileType: st } type PluginConfig = { - fileType: string; + fileType?: string; // 导出的文件类型 + useAliasName?: boolean; // 是否使用 componentName 重命名组件 identifier }; const pluginFactory: BuilderComponentPluginFactory = (config?: PluginConfig) => { - const cfg: PluginConfig = { + const cfg = { fileType: FileType.JS, - ...config, + useAliasName: true, + ...(config || {}), }; const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { @@ -147,7 +175,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?: Plu const packs = groupDepsByPack(ir.deps); Object.keys(packs).forEach((pkg) => { - const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType); + const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType, cfg.useAliasName); next.chunks.push(...chunks); }); } diff --git a/packages/code-generator/src/plugins/common/requireUtils.ts b/packages/code-generator/src/plugins/common/requireUtils.ts index 6d2fcfe92..4dacf68e5 100644 --- a/packages/code-generator/src/plugins/common/requireUtils.ts +++ b/packages/code-generator/src/plugins/common/requireUtils.ts @@ -1,12 +1,6 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; -import { - BuilderComponentPlugin, - BuilderComponentPluginFactory, - ChunkType, - FileType, - ICodeStruct, -} from '../../types'; +import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct } from '../../types'; // TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { diff --git a/packages/code-generator/src/plugins/component/rax/commonDeps.ts b/packages/code-generator/src/plugins/component/rax/commonDeps.ts new file mode 100644 index 000000000..4a2c53bdd --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/commonDeps.ts @@ -0,0 +1,35 @@ +import { COMMON_CHUNK_NAME } from '../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, +} from '../../../types'; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: COMMON_CHUNK_NAME.ExternalDepsImport, + content: ` + // 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。 + // 例外:rax 框架的导出名和各种组件名除外。 + import { createElement, Component } from 'rax'; + import { withRouter as __$$withRouter } from 'rax-app'; + `, + linkAfter: [], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/rax/const.ts b/packages/code-generator/src/plugins/component/rax/const.ts new file mode 100644 index 000000000..a0a07b550 --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/const.ts @@ -0,0 +1,18 @@ +export const RAX_CHUNK_NAME = { + ClassDidMountBegin: 'RaxComponentClassDidMountBegin', + ClassDidMountContent: 'RaxComponentClassDidMountContent', + ClassDidMountEnd: 'RaxComponentClassDidMountEnd', + ClassWillUnmountBegin: 'RaxComponentClassWillUnmountBegin', + ClassWillUnmountContent: 'RaxComponentClassWillUnmountContent', + ClassWillUnmountEnd: 'RaxComponentClassWillUnmountEnd', + ClassRenderBegin: 'RaxComponentClassRenderBegin', + ClassRenderPre: 'RaxComponentClassRenderPre', + ClassRenderJSX: 'RaxComponentClassRenderJSX', + ClassRenderEnd: 'RaxComponentClassRenderEnd', + MethodsBegin: 'RaxComponentMethodsBegin', + MethodsContent: 'RaxComponentMethodsContent', + MethodsEnd: 'RaxComponentMethodsEnd', + LifeCyclesBegin: 'RaxComponentLifeCyclesBegin', + LifeCyclesContent: 'RaxComponentLifeCyclesContent', + LifeCyclesEnd: 'RaxComponentLifeCyclesEnd', +}; diff --git a/packages/code-generator/src/plugins/component/rax/containerClass.ts b/packages/code-generator/src/plugins/component/rax/containerClass.ts new file mode 100644 index 000000000..8104e53e8 --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/containerClass.ts @@ -0,0 +1,144 @@ +import changeCase from 'change-case'; +import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; +import { RAX_CHUNK_NAME } from './const'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IContainerInfo, +} from '../../../types'; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IContainerInfo; + + // 将模块名转换成 PascalCase 的格式,并添加特定后缀,防止命名冲突 + const componentClassName = `${changeCase.pascalCase(ir.moduleName)}$$Page`; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: CLASS_DEFINE_CHUNK_NAME.Start, + content: `class ${componentClassName} extends Component {`, + 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, + name: CLASS_DEFINE_CHUNK_NAME.End, + content: '}', + linkAfter: [ + CLASS_DEFINE_CHUNK_NAME.Start, + CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + RAX_CHUNK_NAME.ClassRenderEnd, + RAX_CHUNK_NAME.MethodsEnd, + ], + }); + + // next.chunks.push({ + // type: ChunkType.STRING, + // fileType: FileType.JSX, + // name: CLASS_DEFINE_CHUNK_NAME.ConstructorStart, + // content: 'constructor(props, context) { super(props); ', + // linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + // }); + + // next.chunks.push({ + // type: ChunkType.STRING, + // fileType: FileType.JSX, + // name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, + // content: '}', + // linkAfter: [ + // CLASS_DEFINE_CHUNK_NAME.ConstructorStart, + // CLASS_DEFINE_CHUNK_NAME.ConstructorContent, + // ], + // }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: RAX_CHUNK_NAME.ClassDidMountBegin, + content: 'componentDidMount() {', + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.InsVar, CLASS_DEFINE_CHUNK_NAME.InsMethod], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: RAX_CHUNK_NAME.ClassDidMountEnd, + content: '}', + linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin, RAX_CHUNK_NAME.ClassDidMountContent], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: RAX_CHUNK_NAME.ClassWillUnmountBegin, + content: 'componentWillUnmount() {', + linkAfter: [ + CLASS_DEFINE_CHUNK_NAME.Start, + CLASS_DEFINE_CHUNK_NAME.InsVar, + CLASS_DEFINE_CHUNK_NAME.InsMethod, + RAX_CHUNK_NAME.ClassDidMountEnd, + ], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: RAX_CHUNK_NAME.ClassWillUnmountEnd, + content: '}', + linkAfter: [RAX_CHUNK_NAME.ClassWillUnmountBegin, RAX_CHUNK_NAME.ClassWillUnmountContent], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: RAX_CHUNK_NAME.ClassRenderBegin, + content: 'render() {', + linkAfter: [RAX_CHUNK_NAME.ClassDidMountEnd, RAX_CHUNK_NAME.ClassWillUnmountEnd], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: RAX_CHUNK_NAME.ClassRenderEnd, + content: '}', + linkAfter: [RAX_CHUNK_NAME.ClassRenderBegin, RAX_CHUNK_NAME.ClassRenderPre, RAX_CHUNK_NAME.ClassRenderJSX], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: COMMON_CHUNK_NAME.FileExport, + content: `export default __$$withRouter(${componentClassName});`, + 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, + ], + }); + + 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 new file mode 100644 index 000000000..9974281bb --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/containerInitState.ts @@ -0,0 +1,68 @@ +import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; + +import { generateCompositeType } from '../../../utils/compositeType'; +import Scope from '../../../utils/Scope'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IContainerInfo, +} from '../../../types'; + +type PluginConfig = { + fileType: string; + implementType: 'inConstructor' | 'insMember' | 'hooks'; +}; + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + implementType: 'insMember', + ...config, + }; + + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IContainerInfo; + const scope = Scope.createRootScope(); + + if (ir.state) { + const state = ir.state; + const fields = Object.keys(state).map((stateName) => { + // TODO: 这里用什么 handlers? + const value = generateCompositeType(state[stateName], scope); + return `${stateName}: ${value}`; + }); + + if (cfg.implementType === 'inConstructor') { + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, + content: `this.state = { ${fields.join(',')} };`, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]], + }); + } else if (cfg.implementType === 'insMember') { + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: `state = { ${fields.join(',')} };`, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], + }); + } + // TODO: hooks state?? + } + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/rax/containerInjectContext.ts b/packages/code-generator/src/plugins/component/rax/containerInjectContext.ts new file mode 100644 index 000000000..ebceb424e --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/containerInjectContext.ts @@ -0,0 +1,116 @@ +import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, +} 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, + }; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: COMMON_CHUNK_NAME.InternalDepsImport, + content: 'import __$$constants from \'../../constants\';', + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], + }); + + // TODO: i18n 是可选的,如果没有 i18n 这个文件怎么办?该怎么判断? + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: COMMON_CHUNK_NAME.InternalDepsImport, + content: 'import * as __$$i18n from \'../../i18n\';', + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: ` + _context = this._createContext(); + `, + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + content: ` + _createContext() { + const self = this; + + // 保存下最新的状态,这样 setState 可以搞成同步一样的了 + self._latestState = self.state; + + const context = { + get state() { + // 这里直接获取最新的 state,从而能避免一些 React/Rax 这样的框架因为异步 setState 而导致的一些问题 + return self._latestState; + }, + setState(newState) { + self._latestState = { ...self._latestState, ...newState }; + self.setState(newState); + }, + get dataSourceMap() { + return self._dataSourceEngine.dataSourceMap || {}; + }, + async reloadDataSource() { + await self._dataSourceEngine.reloadDataSource(); + }, + get utils() { + return self._utils; + }, + get page() { + return context; + }, + get component() { + return context; + }, + get props() { + return self.props; + }, + get constants() { + return __$$constants; + }, + i18n: __$$i18n.i18n, + i18nFormat: __$$i18n.i18nFormat, + getLocale: __$$i18n.getLocale, + setLocale(locale) { + __$$i18n.setLocale(locale); + self.forceUpdate(); + }, + ...this._methods, + }; + + return context; + } + `, + linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts b/packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts new file mode 100644 index 000000000..dd811d52a --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts @@ -0,0 +1,157 @@ +/* eslint-disable @typescript-eslint/indent */ + +import { CompositeValue, JSExpression, DataSourceConfig, isJSExpression, isJSFunction } from '@ali/lowcode-types'; +import changeCase from 'change-case'; + +import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator'; +import Scope from '../../../utils/Scope'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IScope, +} from '../../../types'; + +import { generateCompositeType } from '../../../utils/compositeType'; +import { parseExpressionConvertThis2Context } from '../../../utils/expressionParser'; +import { isContainerSchema } from '../../../utils/schema'; +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 scope = Scope.createRootScope(); + const dataSourceConfig = isContainerSchema(pre.ir) ? pre.ir.dataSource : null; + const dataSourceItems: DataSourceConfig[] = (dataSourceConfig && dataSourceConfig.list) || []; + const dataSourceEngineOptions = { runtimeConfig: true }; + if (dataSourceItems.length > 0) { + const requestHandlersMap = {} as Record; + + dataSourceItems.forEach((ds) => { + const dsType = ds.type || 'fetch'; + if (!(dsType in requestHandlersMap) && dsType !== 'custom') { + const handlerFactoryName = '__$$create' + changeCase.pascal(dsType) + 'RequestHandler'; + + requestHandlersMap[dsType] = { + type: 'JSExpression', + value: handlerFactoryName + (dsType === 'urlParams' ? '(this.props.location.search)' : '()'), + }; + + const handlerFactoryExportName = `create${changeCase.pascal(dsType)}Handler`; + const handlerPkgName = `@ali/lowcode-datasource-${changeCase.kebab(dsType)}-handler`; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: COMMON_CHUNK_NAME.ExternalDepsImport, + content: ` + import { ${handlerFactoryExportName} as ${handlerFactoryName} } from '${handlerPkgName}'; + `, + linkAfter: [], + }); + } + }); + + Object.assign(dataSourceEngineOptions, { requestHandlersMap }); + } + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: COMMON_CHUNK_NAME.ExternalDepsImport, + content: ` + import { create as __$$createDataSourceEngine } from '@ali/lowcode-datasource-engine/runtime'; + `, + linkAfter: [], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: ` + _dataSourceConfig = this._defineDataSourceConfig(); + _dataSourceEngine = __$$createDataSourceEngine( + this._dataSourceConfig, + this._context, + ${generateCompositeType(dataSourceEngineOptions, scope)} + );`, + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.ClassDidMountContent, + content: ` + this._dataSourceEngine.reloadDataSource(); + `, + linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + content: ` +_defineDataSourceConfig() { + const __$$context = this._context; + return (${generateCompositeType( + { + ...dataSourceConfig, + list: [ + ...dataSourceItems.map((item) => ({ + ...item, + isInit: wrapAsFunction(item.isInit, scope), + options: wrapAsFunction(item.options, scope), + })), + ], + }, + scope, + { + handlers: { + function: (jsFunc) => parseExpressionConvertThis2Context(jsFunc.value, '__$$context'), + expression: (jsExpr) => parseExpressionConvertThis2Context(jsExpr.value, '__$$context'), + }, + }, + )}); +} + `, + linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; + +function wrapAsFunction(value: CompositeValue, scope: IScope): CompositeValue { + if (isJSExpression(value) || isJSFunction(value)) { + return { + type: 'JSExpression', + value: `function(){ return ((${value.value}))}`, + }; + } + + return { + type: 'JSExpression', + value: `function(){return((${generateCompositeType(value, scope)}))}`, + }; +} diff --git a/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts b/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts new file mode 100644 index 000000000..6ab39657d --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts @@ -0,0 +1,74 @@ +import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, +} 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, + }; + + 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, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: '_utils = this._defineUtils();', + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + + // 绑定下上下文,这样在所有的 utils 里面都能通过 this.xxx 来访问上下文了 + content: ` + _defineUtils() { + 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], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts b/packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts new file mode 100644 index 000000000..c67ee257e --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts @@ -0,0 +1,120 @@ +import _ from 'lodash'; + +import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; +import { RAX_CHUNK_NAME } from './const'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + FileType, + ChunkType, + ICodeStruct, + IContainerInfo, +} from '../../../types'; + +type PluginConfig = { + fileType: string; + exportNameMapping: Record; + normalizeNameMapping: Record; +}; + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + exportNameMapping: {}, + normalizeNameMapping: {}, + ...config, + }; + + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + // Rax 先只支持 didMount 和 willUnmount 吧 + + const ir = next.ir as IContainerInfo; + const lifeCycles = ir.lifeCycles; + + if (lifeCycles && !_.isEmpty(lifeCycles)) { + Object.entries(lifeCycles).forEach(([lifeCycleName, lifeCycleMethodExpr]) => { + const normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName; + const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.LifeCyclesContent, + content: `${exportName}: (${lifeCycleMethodExpr.value}),`, + linkAfter: [RAX_CHUNK_NAME.LifeCyclesBegin], + }); + + if (normalizeName === 'didMount') { + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.ClassDidMountContent, + content: `this._lifeCycles.${exportName}();`, + linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin], + }); + } else if (normalizeName === 'willUnmount') { + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.ClassWillUnmountContent, + content: `this._lifeCycles.${exportName}();`, + linkAfter: [RAX_CHUNK_NAME.ClassWillUnmountBegin], + }); + } else { + // TODO: print warnings? Unknown life cycle + } + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: '_lifeCycles = this._defineLifeCycles();', + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.LifeCyclesBegin, + content: ` + _defineLifeCycles() { + const __$$lifeCycles = ({ + `, + linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd, CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.LifeCyclesEnd, + content: ` + }); + + // 为所有的方法绑定上下文 + Object.entries(__$$lifeCycles).forEach(([lifeCycleName, lifeCycleMethod]) => { + if (typeof lifeCycleMethod === 'function') { + __$$lifeCycles[lifeCycleName] = (...args) => { + return lifeCycleMethod.apply(this._context, args); + } + } + }); + + return __$$lifeCycles; + } + `, + linkAfter: [RAX_CHUNK_NAME.LifeCyclesBegin, RAX_CHUNK_NAME.LifeCyclesContent], + }); + } + + 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..042462456 --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/containerMethods.ts @@ -0,0 +1,95 @@ +import { CLASS_DEFINE_CHUNK_NAME } 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() { + const __$$methods = ({ + `, + linkAfter: [ + RAX_CHUNK_NAME.ClassRenderEnd, + CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + RAX_CHUNK_NAME.LifeCyclesEnd, + ], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.MethodsEnd, + content: ` + }); + + // 为所有的方法绑定上下文 + Object.entries(__$$methods).forEach(([methodName, method]) => { + if (typeof method === 'function') { + __$$methods[methodName] = (...args) => { + return method.apply(this._context, args); + } + } + }); + + return __$$methods; + } + `, + 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 new file mode 100644 index 000000000..3ee0ff872 --- /dev/null +++ b/packages/code-generator/src/plugins/component/rax/jsx.ts @@ -0,0 +1,350 @@ +import { NodeSchema, JSExpression, NpmInfo, CompositeValue, isJSExpression } from '@ali/lowcode-types'; + +import _ from 'lodash'; +import changeCase from 'change-case'; +import { Expression } from '@babel/types'; +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + CodePiece, + FileType, + ICodeChunk, + ICodeStruct, + IContainerInfo, + PIECE_TYPE, + HandlerSet, + IScope, + NodeGeneratorConfig, + NodePlugin, + AttrPlugin, +} from '../../../types'; + +import { RAX_CHUNK_NAME } from './const'; +import { COMMON_CHUNK_NAME } from '../../../const/generator'; + +import { generateExpression } from '../../../utils/jsExpression'; +import { createNodeGenerator, generateConditionReactCtrl, generateReactExprInJS } from '../../../utils/nodeToJSX'; +import { generateCompositeType } from '../../../utils/compositeType'; +import Scope from '../../../utils/Scope'; +import { + parseExpression, + parseExpressionConvertThis2Context, + parseExpressionGetGlobalVariables, +} from '../../../utils/expressionParser'; + +type PluginConfig = { + fileType: string; +}; + +// TODO: componentName 若并非大写字符打头,甚至并非是一个有效的 JS 标识符怎么办?? +// FIXME: 我想了下,这块应该放到解析阶段就去做掉,对所有 componentName 做 identifier validate,然后对不合法的做统一替换。 +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; + const rootScope = Scope.createRootScope(); + + // Rax 构建到小程序的时候,不能给组件起起别名,得直接引用,故这里将所有的别名替换掉 + // 先收集下所有的 alias 的映射 + const componentsNameAliasMap = new Map(); + next.chunks.forEach((chunk) => { + if (isImportAliasDefineChunk(chunk)) { + componentsNameAliasMap.set(chunk.ext.aliasName, chunk.ext.originalName); + } + }); + + // 注意:这里其实隐含了一个假设:schema 中的 componentName 应该是一个有效的 JS 标识符,而且是大写字母打头的 + const mapComponentNameToAliasOrKeepIt = + (componentName: string) => componentsNameAliasMap.get(componentName) || componentName; + + // 然后过滤掉所有的别名 chunks + next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk)); + + // 如果直接按目前的 React 的方式之间出码 JSX 的话,会有 3 个问题: + // 1. 小程序出码的时候,循环变量没法拿到 + // 2. 小程序出码的时候,很容易出现 Uncaught TypeError: Cannot read property 'avatar' of undefined + // 这样的异常(如下图的 50 行) -- 因为若直接出码,Rax 构建到小程序的时候会立即计算所有在视图中用到的变量 + // 3. 通过 this.xxx 能拿到的东西太多了,而且自定义的 methods 可能会无意间破坏 Rax 框架或小程序框架在页面 this 上的东东 + const customHandlers: HandlerSet = { + expression(input: JSExpression, scope: IScope) { + return transformJsExpr(generateExpression(input), scope); + }, + function(input, scope: IScope) { + return transformThis2Context(input.value || 'null', scope); + }, + }; + + // 创建代码生成器 + const commonNodeGenerator = createNodeGenerator({ + handlers: customHandlers, + tagMapping: mapComponentNameToAliasOrKeepIt, + nodePlugins: [generateReactExprInJS, generateConditionReactCtrl, generateRaxLoopCtrl], + attrPlugins: [generateNodeAttrForRax], + }); + + // 生成 JSX 代码 + const jsxContent = commonNodeGenerator(ir, rootScope); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: COMMON_CHUNK_NAME.ExternalDepsImport, + content: 'import { isMiniApp as __$$isMiniApp } from \'universal-env\';', + linkAfter: [], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: RAX_CHUNK_NAME.ClassRenderPre, + // TODO: setState, dataSourceMap, reloadDataSource, utils, + // i18n, i18nFormat, getLocale, setLocale + // 这些在 Rax 的编译模式下不能在视图中直接访问,需要转化成 this.xxx + content: ` + const __$$context = this._context; + const { state, setState, dataSourceMap, reloadDataSource, utils, constants, i18n, i18nFormat, getLocale, setLocale } = __$$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.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 transformJsExpr(expr: string, scope: IScope) { + if (!expr) { + return 'undefined'; + } + + if (isLiteralAtomicExpr(expr)) { + return expr; + } + + const exprAst = parseExpression(expr); + + // 对于下面这些比较安全的字面值,可以直接返回对应的表达式,而非包一层 + if (isSimpleStraightLiteral(exprAst)) { + return expr; + } + + switch (exprAst.type) { + // 对于直接写个函数的,则不用再包下,因为这样不会抛出异常的 + case 'ArrowFunctionExpression': + case 'FunctionExpression': + return transformThis2Context(exprAst, scope); + + default: + break; + } + + // 其他的都需要包一层 + return `__$$eval(() => (${transformThis2Context(exprAst, scope)}))`; +} + +/** 判断是非是一些简单直接的字面值 */ +function isSimpleStraightLiteral(expr: Expression): boolean { + switch (expr.type) { + case 'BigIntLiteral': + case 'BooleanLiteral': + case 'DecimalLiteral': + case 'NullLiteral': + case 'NumericLiteral': + case 'RegExpLiteral': + case 'StringLiteral': + return true; + default: + return false; + } +} + +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+(\.\d+)?$/.test(expr); +} + +/** + * 将所有的 this.xxx 替换为 __$$context.xxx + * @param expr + */ +function transformThis2Context(expr: string | Expression, scope: IScope): string { + // 下面这种字符串替换的方式虽然简单直接,但是对于复杂场景会误匹配,故后期改成了解析 AST 然后修改 AST 最后再重新生成代码的方式 + // return expr + // .replace(/\bthis\.item\./g, () => 'item.') + // .replace(/\bthis\.index\./g, () => 'index.') + // .replace(/\bthis\./g, () => '__$$context.'); + + return parseExpressionConvertThis2Context(expr, '__$$context', scope.bindings?.getAllBindings() || []); +} + +function generateRaxLoopCtrl( + nodeItem: NodeSchema, + scope: IScope, + config?: NodeGeneratorConfig, + next?: NodePlugin, +): CodePiece[] { + if (nodeItem.loop) { + const loopItemName = nodeItem.loopArgs?.[0] || 'item'; + const loopIndexName = nodeItem.loopArgs?.[1] || 'index'; + const subScope = scope.createSubScope([loopItemName, loopIndexName]); + const pieces: CodePiece[] = next ? next(nodeItem, subScope, config) : []; + + const loopDataExpr = `__$$evalArray(() => (${transformThis2Context( + generateCompositeType(nodeItem.loop, scope, { handlers: config?.handlers }), + scope, + )}))`; + + pieces.unshift({ + value: `${loopDataExpr}.map((${loopItemName}, ${loopIndexName}) => (`, + type: PIECE_TYPE.BEFORE, + }); + + pieces.push({ + value: '))', + type: PIECE_TYPE.AFTER, + }); + + return pieces; + } + + return next ? next(nodeItem, scope, config) : []; +} + +function generateNodeAttrForRax( + attrData: { attrName: string; attrValue: CompositeValue }, + scope: IScope, + config?: NodeGeneratorConfig, + next?: AttrPlugin, +): CodePiece[] { + if (!/^on/.test(attrData.attrName)) { + return next ? next(attrData, scope, config) : []; + } + // else: onXxx 的都是事件处理函数需要特殊处理下 + return generateEventHandlerAttrForRax(attrData.attrName, attrData.attrValue, scope, config); +} + +function generateEventHandlerAttrForRax( + attrName: string, + attrValue: CompositeValue, + scope: IScope, + config?: NodeGeneratorConfig, +): CodePiece[] { + // -- 事件处理函数中 JSExpression 转成 JSFunction 来处理,避免当 JSExpression 处理的时候多包一层 eval 而导致 Rax 转码成小程序的时候出问题 + const valueExpr = generateCompositeType( + isJSExpression(attrValue) ? { type: 'JSFunction', value: attrValue.value } : attrValue, + scope, + { + handlers: config?.handlers, + }, + ); + + // 查询当前作用域下的变量 + const currentScopeVariables = scope.bindings?.getAllBindings() || []; + if (currentScopeVariables.length <= 0) { + return [ + { + type: PIECE_TYPE.ATTR, + value: `${attrName}={${valueExpr}}`, + }, + ]; + } + + // 提取出所有的未定义的全局变量 + const undeclaredVariablesInValueExpr = parseExpressionGetGlobalVariables(valueExpr); + const referencedLocalVariables = + _.intersection(undeclaredVariablesInValueExpr, currentScopeVariables); + if (referencedLocalVariables.length <= 0) { + return [ + { + type: PIECE_TYPE.ATTR, + value: `${attrName}={${valueExpr}}`, + }, + ]; + } + + const wrappedAttrValueExpr = [ + '(...__$$args) => {', + ' if (__$$isMiniApp) {', + ' const __$$event = __$$args[0];', + ...referencedLocalVariables.map((localVar) => `const ${localVar} = __$$event.target.dataset.${localVar};`), + ` return (${valueExpr}).apply(this, __$$args);`, + ' } else {', + ` return (${valueExpr}).apply(this, __$$args);`, + ' }', + '}', + ].join('\n'); + + return [ + ...referencedLocalVariables.map((localVar) => ({ + type: PIECE_TYPE.ATTR, + value: `data-${changeCase.snake(localVar)}={${localVar}}`, + })), + { + type: PIECE_TYPE.ATTR, + value: `${attrName}={${wrappedAttrValueExpr}}`, + }, + ]; +} diff --git a/packages/code-generator/src/plugins/component/react/containerClass.ts b/packages/code-generator/src/plugins/component/react/containerClass.ts index b84e7aa6e..6ba6ae640 100644 --- a/packages/code-generator/src/plugins/component/react/containerClass.ts +++ b/packages/code-generator/src/plugins/component/react/containerClass.ts @@ -1,3 +1,4 @@ +import changeCase from 'change-case'; import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; import { REACT_CHUNK_NAME } from './const'; @@ -18,14 +19,18 @@ const pluginFactory: BuilderComponentPluginFactory = () => { const ir = next.ir as IContainerInfo; + // 将模块名转换成 PascalCase 的格式,并添加特定后缀,防止命名冲突 + const componentClassName = `${changeCase.pascalCase(ir.moduleName)}$$Page`; + next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.Start, - content: `class ${ir.moduleName} extends React.Component {`, + content: `class ${componentClassName} extends React.Component {`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, ], @@ -36,7 +41,10 @@ const pluginFactory: BuilderComponentPluginFactory = () => { fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.End, content: '}', - linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start, REACT_CHUNK_NAME.ClassRenderEnd], + linkAfter: [ + CLASS_DEFINE_CHUNK_NAME.Start, + REACT_CHUNK_NAME.ClassRenderEnd, + ], }); next.chunks.push({ @@ -52,10 +60,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { fileType: FileType.JSX, name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, content: '}', - linkAfter: [ - CLASS_DEFINE_CHUNK_NAME.ConstructorStart, - CLASS_DEFINE_CHUNK_NAME.ConstructorContent, - ], + linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart, CLASS_DEFINE_CHUNK_NAME.ConstructorContent], }); next.chunks.push({ @@ -75,21 +80,18 @@ const pluginFactory: BuilderComponentPluginFactory = () => { fileType: FileType.JSX, name: REACT_CHUNK_NAME.ClassRenderEnd, content: '}', - linkAfter: [ - REACT_CHUNK_NAME.ClassRenderStart, - REACT_CHUNK_NAME.ClassRenderPre, - REACT_CHUNK_NAME.ClassRenderJSX, - ], + linkAfter: [REACT_CHUNK_NAME.ClassRenderStart, REACT_CHUNK_NAME.ClassRenderPre, REACT_CHUNK_NAME.ClassRenderJSX], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.FileExport, - content: `export default ${ir.moduleName};`, + content: `export default ${componentClassName};`, 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/react/containerDataSource.ts b/packages/code-generator/src/plugins/component/react/containerDataSource.ts index 8907dea36..84accc118 100644 --- a/packages/code-generator/src/plugins/component/react/containerDataSource.ts +++ b/packages/code-generator/src/plugins/component/react/containerDataSource.ts @@ -1,6 +1,7 @@ import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; import { generateCompositeType } from '../../../utils/compositeType'; +import Scope from '../../../utils/Scope'; import { BuilderComponentPlugin, @@ -28,10 +29,12 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => const ir = next.ir as IContainerInfo; + const scope = Scope.createRootScope(); + if (ir.state) { - const { state } = ir; + const state = ir.state; const fields = Object.keys(state).map((stateName) => { - const value = generateCompositeType(state[stateName]); + const value = generateCompositeType(state[stateName], scope); return `${stateName}: ${value},`; }); diff --git a/packages/code-generator/src/plugins/component/react/containerInitState.ts b/packages/code-generator/src/plugins/component/react/containerInitState.ts index 3ab10463c..503e7ed0f 100644 --- a/packages/code-generator/src/plugins/component/react/containerInitState.ts +++ b/packages/code-generator/src/plugins/component/react/containerInitState.ts @@ -1,6 +1,7 @@ import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; import { generateCompositeType } from '../../../utils/compositeType'; +import Scope from '../../../utils/Scope'; import { BuilderComponentPlugin, @@ -29,11 +30,12 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => }; const ir = next.ir as IContainerInfo; + const scope = Scope.createRootScope(); if (ir.state) { - const { state } = ir; + const state = ir.state; const fields = Object.keys(state).map((stateName) => { - const value = generateCompositeType(state[stateName]); + const value = generateCompositeType(state[stateName], scope); return `${stateName}: ${value},`; }); diff --git a/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts b/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts index 585cd6d7e..000355b7d 100644 --- a/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts +++ b/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts @@ -35,7 +35,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => const ir = next.ir as IContainerInfo; if (ir.lifeCycles) { - const { lifeCycles } = ir; + const lifeCycles = ir.lifeCycles; const chunks = Object.keys(lifeCycles).map((lifeCycleName) => { const normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName; const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName; diff --git a/packages/code-generator/src/plugins/component/react/containerMethod.ts b/packages/code-generator/src/plugins/component/react/containerMethod.ts index ecc89d828..148f2cffd 100644 --- a/packages/code-generator/src/plugins/component/react/containerMethod.ts +++ b/packages/code-generator/src/plugins/component/react/containerMethod.ts @@ -30,7 +30,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => const ir = next.ir as IContainerInfo; if (ir.methods) { - const { methods } = ir; + const methods = ir.methods; const chunks = Object.keys(methods).map((methodName) => ({ type: ChunkType.STRING, fileType: cfg.fileType, diff --git a/packages/code-generator/src/plugins/component/react/jsx.ts b/packages/code-generator/src/plugins/component/react/jsx.ts index 78ee3f790..ada4be7b7 100644 --- a/packages/code-generator/src/plugins/component/react/jsx.ts +++ b/packages/code-generator/src/plugins/component/react/jsx.ts @@ -10,6 +10,7 @@ import { import { REACT_CHUNK_NAME } from './const'; import { createReactNodeGenerator } from '../../../utils/nodeToJSX'; +import Scope from '../../../utils/Scope'; type PluginConfig = { fileType?: string; @@ -23,7 +24,11 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => ...config, }; - const generator = createReactNodeGenerator({ nodeTypeMapping: cfg.nodeTypeMapping }); + const { nodeTypeMapping } = cfg; + + const generator = createReactNodeGenerator({ + tagMapping: (v) => nodeTypeMapping[v] || v, + }); const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { @@ -31,17 +36,15 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => }; const ir = next.ir as IContainerInfo; - const jsxContent = generator(ir); + const scope = Scope.createRootScope(); + const jsxContent = generator(ir, scope); next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: REACT_CHUNK_NAME.ClassRenderJSX, content: `return ${jsxContent};`, - linkAfter: [ - REACT_CHUNK_NAME.ClassRenderStart, - REACT_CHUNK_NAME.ClassRenderPre, - ], + linkAfter: [REACT_CHUNK_NAME.ClassRenderStart, REACT_CHUNK_NAME.ClassRenderPre], }); return next; diff --git a/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts b/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts index 51af22e4f..2e71f8d90 100644 --- a/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts +++ b/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts @@ -6,7 +6,6 @@ import { ChunkType, FileType, ICodeStruct, - IContainerInfo, } from '../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { diff --git a/packages/code-generator/src/plugins/component/recore/const.ts b/packages/code-generator/src/plugins/component/recore/const.ts deleted file mode 100644 index b848f42aa..000000000 --- a/packages/code-generator/src/plugins/component/recore/const.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const RECORE_CHUNK_NAME = { - -}; diff --git a/packages/code-generator/src/plugins/component/recore/pageDataSource.ts b/packages/code-generator/src/plugins/component/recore/pageDataSource.ts index 8464ff180..a071f65c9 100644 --- a/packages/code-generator/src/plugins/component/recore/pageDataSource.ts +++ b/packages/code-generator/src/plugins/component/recore/pageDataSource.ts @@ -1,4 +1,7 @@ +import { JSExpression, CompositeValue } from '@ali/lowcode-types'; + import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; +import Scope from '../../../utils/Scope'; import { BuilderComponentPlugin, @@ -7,15 +10,13 @@ import { FileType, ICodeStruct, IContainerInfo, - IJSExpression, - CompositeValue, } from '../../../types'; import { generateCompositeType } from '../../../utils/compositeType'; import { generateExpression } from '../../../utils/jsExpression'; function packJsExpression(exp: unknown): string { - const expression = exp as IJSExpression; + const expression = exp as JSExpression; const funcStr = generateExpression(expression); return `function() { return (${funcStr}); }`; } @@ -27,6 +28,8 @@ const pluginFactory: BuilderComponentPluginFactory = () => { }; const ir = next.ir as IContainerInfo; + const scope = Scope.createRootScope(); + if (ir.dataSource) { const { dataSource } = ir; const { list, ...rest } = dataSource; @@ -35,13 +38,13 @@ const pluginFactory: BuilderComponentPluginFactory = () => { const extConfigs = Object.keys(rest).map((extConfigName) => { const value = (rest as Record)[extConfigName]; - const valueStr = generateCompositeType(value); + const valueStr = generateCompositeType(value, scope); return `${extConfigName}: ${valueStr}`; }); attrs = [...attrs, ...extConfigs]; - const listProp = generateCompositeType((list as unknown) as CompositeValue, { + const listProp = generateCompositeType((list as unknown) as CompositeValue, scope, { handlers: { expression: packJsExpression, }, diff --git a/packages/code-generator/src/plugins/component/recore/pageStyle.ts b/packages/code-generator/src/plugins/component/recore/pageStyle.ts index fc8ca41b5..2a6a8eeca 100644 --- a/packages/code-generator/src/plugins/component/recore/pageStyle.ts +++ b/packages/code-generator/src/plugins/component/recore/pageStyle.ts @@ -22,7 +22,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { type: ChunkType.STRING, fileType: FileType.TS, name: CLASS_DEFINE_CHUNK_NAME.StaticVar, - content: `static cssText = '${ir.css.replace(/\'/g, '\\\'')}';`, + content: `static cssText = '${ir.css.replace(/'/g, "\\'")}';`, linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.StaticVar]], }); } diff --git a/packages/code-generator/src/plugins/component/recore/pageVmBody.ts b/packages/code-generator/src/plugins/component/recore/pageVmBody.ts index 9f5a33d7c..e30327372 100644 --- a/packages/code-generator/src/plugins/component/recore/pageVmBody.ts +++ b/packages/code-generator/src/plugins/component/recore/pageVmBody.ts @@ -1,21 +1,22 @@ +import { NodeSchema } from '@ali/lowcode-types'; + import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, ICodeStruct, IContainerInfo, - IComponentNodeItem, - INodeGeneratorContext, + IScope, CodePiece, PIECE_TYPE, } from '../../../types'; import { COMMON_CHUNK_NAME } from '../../../const/generator'; -import { createNodeGenerator, generateString } from '../../../utils/nodeToJSX'; -import { generateExpression } from '../../../utils/jsExpression'; +import { createNodeGenerator } from '../../../utils/nodeToJSX'; import { generateCompositeType } from '../../../utils/compositeType'; +import Scope from '../../../utils/Scope'; -const generateGlobalProps = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem): CodePiece[] => { +const generateGlobalProps = (nodeItem: NodeSchema): CodePiece[] => { return [ { value: `{...globalProps.${nodeItem.componentName}}`, @@ -24,11 +25,11 @@ const generateGlobalProps = (ctx: INodeGeneratorContext, nodeItem: IComponentNod ]; }; -const generateCtrlLine = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeItem): CodePiece[] => { +const generateCtrlLine = (nodeItem: NodeSchema, scope: IScope): CodePiece[] => { const pieces: CodePiece[] = []; if (nodeItem.loop && nodeItem.loopArgs) { - const loopDataExp = generateCompositeType(nodeItem.loop); + const loopDataExp = generateCompositeType(nodeItem.loop, scope); pieces.push({ type: PIECE_TYPE.ATTR, value: `x-for={${loopDataExp}}`, @@ -41,7 +42,7 @@ const generateCtrlLine = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeIt } if (nodeItem.condition) { - const conditionExp = generateCompositeType(nodeItem.condition); + const conditionExp = generateCompositeType(nodeItem.condition, scope); pieces.push({ type: PIECE_TYPE.ATTR, value: `x-if={${conditionExp}}`, @@ -52,13 +53,9 @@ const generateCtrlLine = (ctx: INodeGeneratorContext, nodeItem: IComponentNodeIt }; const pluginFactory: BuilderComponentPluginFactory = () => { - const generator = createNodeGenerator( - { - string: generateString, - expression: (input) => [generateExpression(input)], - }, - [generateGlobalProps, generateCtrlLine], - ); + const generator = createNodeGenerator({ + nodePlugins: [generateGlobalProps, generateCtrlLine], + }); const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { @@ -66,8 +63,9 @@ const pluginFactory: BuilderComponentPluginFactory = () => { }; const ir = next.ir as IContainerInfo; + const scope = Scope.createRootScope(); - const vxContent = generator(ir); + const vxContent = generator(ir, scope); next.chunks.push({ type: ChunkType.STRING, diff --git a/packages/code-generator/src/plugins/component/recore/pageVmHeader.ts b/packages/code-generator/src/plugins/component/recore/pageVmHeader.ts index 71ee2335c..8bfaa3dd1 100644 --- a/packages/code-generator/src/plugins/component/recore/pageVmHeader.ts +++ b/packages/code-generator/src/plugins/component/recore/pageVmHeader.ts @@ -1,11 +1,6 @@ import { COMMON_CHUNK_NAME } from '../../../const/generator'; -import { - BuilderComponentPlugin, - BuilderComponentPluginFactory, - ChunkType, - ICodeStruct, -} from '../../../types'; +import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, ICodeStruct } from '../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { diff --git a/packages/code-generator/src/plugins/project/constants.ts b/packages/code-generator/src/plugins/project/constants.ts index d274499b4..08870e507 100644 --- a/packages/code-generator/src/plugins/project/constants.ts +++ b/packages/code-generator/src/plugins/project/constants.ts @@ -8,6 +8,7 @@ import { ICodeStruct, IProjectInfo, } from '../../types'; +import Scope from '../../utils/Scope'; const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { @@ -16,35 +17,39 @@ const pluginFactory: BuilderComponentPluginFactory = () => { }; const ir = next.ir as IProjectInfo; - if (ir.constants) { - const constantStr = generateCompositeType(ir.constants); + const scope = Scope.createRootScope(); + const constantStr = generateCompositeType(ir.constants || {}, scope); - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileVarDefine, - content: ` - const constantConfig = ${constantStr}; - `, - linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport], - }); + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileVarDefine, + content: ` + const __$$constants = (${constantStr}); + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, + ], + }); - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileExport, - content: ` - export default constantConfig; - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - COMMON_CHUNK_NAME.FileMainContent, - ], - }); - } + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileExport, + content: ` + export default __$$constants; + `, + 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, + ], + }); return next; }; 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..b5dc2d8f8 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 @@ -6,7 +6,6 @@ import { ChunkType, FileType, ICodeStruct, - IProjectInfo, } from '../../../../../types'; const pluginFactory: BuilderComponentPluginFactory = () => { @@ -15,8 +14,6 @@ const pluginFactory: BuilderComponentPluginFactory = () => { ...pre, }; - const ir = next.ir as IProjectInfo; - next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, @@ -34,10 +31,10 @@ const pluginFactory: BuilderComponentPluginFactory = () => { content: ` const appConfig = { app: { - rootId: '${ir.config.targetRootID}', + rootId: 'app', }, router: { - type: '${ir.config.historyMode}', + type: 'hash', }, }; createApp(appConfig); @@ -45,6 +42,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/entryHtml.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/entryHtml.ts index a0ca3cdf1..e5837a8dd 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/plugins/entryHtml.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/entryHtml.ts @@ -28,10 +28,10 @@ const pluginFactory: BuilderComponentPluginFactory = () => { - ${ir.meta.name} + ${ir?.meta?.name || 'Ice App'} -
+
`, diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/packageJSON.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/packageJSON.ts index f238b6af0..c3eb5198d 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/plugins/packageJSON.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/packageJSON.ts @@ -1,3 +1,5 @@ +import { PackageJSON } from '@ali/lowcode-types'; + import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { @@ -6,11 +8,10 @@ import { ChunkType, FileType, ICodeStruct, - IPackageJSON, IProjectInfo, } from '../../../../../types'; -interface IIceJsPackageJSON extends IPackageJSON { +interface IIceJsPackageJSON extends PackageJSON { ideMode: { name: string; }; @@ -73,7 +74,9 @@ const pluginFactory: BuilderComponentPluginFactory = () => { originTemplate: '@alifd/scaffold-lite-js', }; - ir.packages.forEach((packageInfo) => (packageJson.dependencies[packageInfo.package] = packageInfo.version)); + ir.packages.forEach((packageInfo) => { + packageJson.dependencies[packageInfo.package] = packageInfo.version; + }); next.chunks.push({ type: ChunkType.JSON, 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 bb2d7c47f..0e2e8c1f7 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/icejs/template/files/README.md.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/README.md.ts index c5a268127..1fb96bec0 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/README.md.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/README.md.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'README', 'md', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/abc.json.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/abc.json.ts index 9b99a2185..0f9e8a532 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/abc.json.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/abc.json.ts @@ -1,17 +1,17 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( - 'abc', - 'json', - ` +export default function getFile(): [string[], ResultFile] { + return [ + [], + { + name: 'abc', + ext: 'json', + content: ` { "type": "ice-app", "builder": "@ali/builder-ice-app" } `, - ); - - return [[], file]; + }, + ]; } diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/build.json.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/build.json.ts index b30c7c58f..533aea2ad 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/build.json.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/build.json.ts @@ -1,11 +1,12 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( - 'build', - 'json', - ` +export default function getFile(): [string[], ResultFile] { + return [ + [], + { + name: 'build', + ext: 'json', + content: ` { "entry": "src/app.js", "plugins": [ @@ -26,8 +27,7 @@ export default function getFile(): [string[], IResultFile] { "@ali/build-plugin-ice-def" ] } - `, - ); - - return [[], file]; + `, + }, + ]; } diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/editorconfig.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/editorconfig.ts index 14876b397..bf80464dd 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/editorconfig.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/editorconfig.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( '.editorconfig', '', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintignore.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintignore.ts index 8f6f68e1f..a26db9de1 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintignore.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintignore.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( '.eslintignore', '', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintrc.js.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintrc.js.ts index ad7ce4480..733c3e174 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintrc.js.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintrc.js.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( '.eslintrc', 'js', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/gitignore.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/gitignore.ts index e7bed2e9e..408a93205 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/gitignore.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/gitignore.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( '.gitignore', '', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/jsconfig.json.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/jsconfig.json.ts index f5b5e9fa2..cde81c77f 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/jsconfig.json.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/jsconfig.json.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'jsconfig', 'json', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierignore.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierignore.ts index f6d2e9ed2..6206dca28 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierignore.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierignore.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( '.prettierignore', '', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierrc.js.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierrc.js.ts index 66531c8dc..1793891ed 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierrc.js.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierrc.js.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( '.prettierrc', 'js', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts index 639337aa5..131728077 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'index', 'jsx', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.module.scss.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.module.scss.ts index cb57ac646..5093a4cf9 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.module.scss.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.module.scss.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'index', 'module.scss', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts index cef5376ae..efb8377ed 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'index', 'jsx', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.module.scss.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.module.scss.ts index 6969595e6..ebbf1e453 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.module.scss.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.module.scss.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'index', 'module.scss', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts index b722e6695..302d16d71 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'index', 'jsx', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/index.jsx.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/index.jsx.ts index 6f7cd7b92..8a97f76d8 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/index.jsx.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/index.jsx.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'index', 'jsx', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/menuConfig.js.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/menuConfig.js.ts index 70ed04738..12b18285b 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/menuConfig.js.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/menuConfig.js.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'menuConfig', 'js', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintignore.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintignore.ts index 5d369298a..1e7ccc9b3 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintignore.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintignore.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( '.stylelintignore', '', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintrc.js.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintrc.js.ts index 7ed60d2c9..12052b1e1 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintrc.js.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintrc.js.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( '.stylelintrc', 'js', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/tsconfig.json.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/tsconfig.json.ts index 4b8aab7ed..e99b00d06 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/files/tsconfig.json.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/tsconfig.json.ts @@ -1,8 +1,8 @@ -import ResultFile from '../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../types'; +import { ResultFile } from '@ali/lowcode-types'; +import { createResultFile } from '../../../../../../utils/resultHelper'; -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( +export default function getFile(): [string[], ResultFile] { + const file = createResultFile( 'tsconfig', 'json', ` diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/index.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/index.ts index d048bd389..e6469a67e 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/template/index.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/template/index.ts @@ -1,8 +1,7 @@ -import ResultDir from '../../../../../model/ResultDir'; -import { - IProjectTemplate, - IResultDir, -} from '../../../../../types'; +import { ResultDir } from '@ali/lowcode-types'; +import { IProjectTemplate } from '../../../../../types'; + +import { createResultDir } from '../../../../../utils/resultHelper'; import { runFileGenerator } from '../../../../../utils/templateHelper'; import file12 from './files/abc.json'; @@ -68,8 +67,8 @@ const icejsTemplate: IProjectTemplate = { }, }, - generateTemplate(): IResultDir { - const root = new ResultDir('.'); + generateTemplate(): ResultDir { + const root = createResultDir('.'); runFileGenerator(root, file1); runFileGenerator(root, file2); diff --git a/packages/code-generator/src/plugins/project/framework/rax/index.ts b/packages/code-generator/src/plugins/project/framework/rax/index.ts new file mode 100644 index 000000000..a17e82132 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/rax/index.ts @@ -0,0 +1,19 @@ +import template from './template'; +import entry from './plugins/entry'; +import appConfig from './plugins/appConfig'; +import buildConfig from './plugins/buildConfig'; +import entryDocument from './plugins/entryDocument'; +import globalStyle from './plugins/globalStyle'; +import packageJSON from './plugins/packageJSON'; + +export default { + template, + plugins: { + appConfig, + buildConfig, + entry, + entryDocument, + globalStyle, + packageJSON, + }, +}; diff --git a/packages/code-generator/src/plugins/project/framework/rax/plugins/appConfig.ts b/packages/code-generator/src/plugins/project/framework/rax/plugins/appConfig.ts new file mode 100644 index 000000000..354e5f93c --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/rax/plugins/appConfig.ts @@ -0,0 +1,46 @@ +import changeCase from 'change-case'; +import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IParseResult, +} from '../../../../../types'; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IParseResult; + + const routes = ir.globalRouter?.routes?.map((route) => ({ + path: route.path, + source: `pages/${changeCase.pascalCase(route.fileName)}/index`, + })) || [{ path: '/', source: 'pages/Home/index' }]; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSON, + name: COMMON_CHUNK_NAME.CustomContent, + content: ` +{ + "routes": ${JSON.stringify(routes, null, 2)}, + "window": { + "title": ${JSON.stringify(ir.project?.meta?.title || ir.project?.meta?.name || '')} + } +} + `, + linkAfter: [], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/project/framework/rax/plugins/buildConfig.ts b/packages/code-generator/src/plugins/project/framework/rax/plugins/buildConfig.ts new file mode 100644 index 000000000..070db3ee3 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/rax/plugins/buildConfig.ts @@ -0,0 +1,52 @@ +import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IParseResult, +} from '../../../../../types'; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IParseResult; + const miniAppBuildType = ir.project?.config.miniAppBuildType; + + const buildCfg = { + inlineStyle: false, + plugins: [ + [ + 'build-plugin-rax-app', + { + targets: ['web', 'miniapp'], + miniapp: miniAppBuildType + ? { + buildType: miniAppBuildType, + } + : undefined, + }, + ], + '@ali/build-plugin-rax-app-def', + ], + }; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSON, + name: COMMON_CHUNK_NAME.CustomContent, + content: JSON.stringify(buildCfg, null, 2) + '\n', + linkAfter: [], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; 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 new file mode 100644 index 000000000..bea52e2af --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/rax/plugins/entry.ts @@ -0,0 +1,51 @@ +import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, +} from '../../../../../types'; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.ExternalDepsImport, + content: ` +import { runApp } from 'rax-app'; +import appConfig from './app.json'; + +import './global.scss'; +`, + linkAfter: [], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileMainContent, + content: ` +runApp(appConfig); +`, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + ], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/project/framework/rax/plugins/entryDocument.ts b/packages/code-generator/src/plugins/project/framework/rax/plugins/entryDocument.ts new file mode 100644 index 000000000..cb5240139 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/rax/plugins/entryDocument.ts @@ -0,0 +1,59 @@ +import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IProjectInfo, +} from '../../../../../types'; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IProjectInfo; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: COMMON_CHUNK_NAME.CustomContent, + content: ` +import { createElement } from 'rax'; +import { Root, Style, Script } from 'rax-document'; + +function Document() { + return ( + + + + + ${ir?.meta?.name || 'Rax App'} +