From 3bfe7580db15d81560a4fee153e6d493a04ecfd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=A5=E5=B8=8C?= Date: Wed, 22 Apr 2020 23:16:03 +0800 Subject: [PATCH] feat: recore solution --- packages/code-generator/package.json | 6 + .../src/@types/ali__my-prettier/index.d.ts | 6 + .../code-generator/src/const/generator.ts | 101 ++++++++ .../code-generator/src/demo/expressionTest.ts | 22 ++ .../src/generator/ProjectBuilder.ts | 68 ++---- packages/code-generator/src/index.ts | 2 + .../code-generator/src/parser/SchemaParser.ts | 21 +- .../src/plugins/component/react/const.ts | 7 - .../plugins/component/react/containerClass.ts | 31 ++- .../component/react/containerDataSource.ts | 21 +- .../component/react/containerInitState.ts | 41 +++- .../component/react/containerInjectUtils.ts | 19 +- .../component/react/containerLifeCycle.ts | 63 ++--- .../component/react/containerMethod.ts | 25 +- .../src/plugins/component/react/jsx.ts | 125 +--------- .../component/react/reactCommonDeps.ts | 1 - .../src/plugins/component/recore/const.ts | 3 + .../component/recore/pageDataSource.ts | 73 ++++++ .../src/plugins/component/recore/pageFrame.ts | 81 +++++++ .../src/plugins/component/recore/pageStyle.ts | 35 +++ .../plugins/component/recore/pageVmBody.ts | 82 +++++++ .../plugins/component/recore/pageVmHeader.ts | 29 +++ .../src/plugins/component/style/css.ts | 19 +- .../src/plugins/project/constants.ts | 3 +- .../project/framework/icejs/plugins/entry.ts | 1 - .../framework/icejs/plugins/entryHtml.ts | 1 - .../framework/icejs/plugins/globalStyle.ts | 1 - .../framework/icejs/plugins/packageJSON.ts | 1 - .../project/framework/icejs/plugins/router.ts | 1 - .../project/framework/icejs/template/index.ts | 25 +- .../recore/template/files/README.md.ts | 5 +- .../recore/template/files/package.json.ts | 19 +- .../recore/template/files/src/index.ts.ts | 73 ++++++ .../template/files/src/plugins/provider.ts.ts | 89 ------- .../recore/template/files/src/router.ts.ts | 3 +- .../framework/recore/template/index.ts | 54 +++++ .../src/plugins/project/i18n.ts | 3 +- .../src/plugins/project/utils.ts | 1 - .../src/plugins/utils/compositeType.ts | 45 ---- .../src/plugins/utils/jsExpression.ts | 39 ---- .../src/postprocessor/prettier/index.ts | 20 +- .../code-generator/src/solutions/recore.ts | 51 ++++ packages/code-generator/src/types/core.ts | 61 +++-- .../code-generator/src/types/intermediate.ts | 2 +- packages/code-generator/src/types/schema.ts | 10 +- packages/code-generator/src/utils/children.ts | 35 --- .../code-generator/src/utils/compositeType.ts | 88 +++++++ .../code-generator/src/utils/jsExpression.ts | 85 +++++++ .../code-generator/src/utils/nodeToJSX.ts | 221 ++++++++++++++++++ .../src/utils/templateHelper.ts | 25 ++ 50 files changed, 1293 insertions(+), 550 deletions(-) create mode 100644 packages/code-generator/src/@types/ali__my-prettier/index.d.ts create mode 100644 packages/code-generator/src/demo/expressionTest.ts create mode 100644 packages/code-generator/src/plugins/component/recore/const.ts create mode 100644 packages/code-generator/src/plugins/component/recore/pageDataSource.ts create mode 100644 packages/code-generator/src/plugins/component/recore/pageFrame.ts create mode 100644 packages/code-generator/src/plugins/component/recore/pageStyle.ts create mode 100644 packages/code-generator/src/plugins/component/recore/pageVmBody.ts create mode 100644 packages/code-generator/src/plugins/component/recore/pageVmHeader.ts delete mode 100644 packages/code-generator/src/plugins/project/framework/recore/template/files/src/plugins/provider.ts.ts create mode 100644 packages/code-generator/src/plugins/project/framework/recore/template/index.ts delete mode 100644 packages/code-generator/src/plugins/utils/compositeType.ts delete mode 100644 packages/code-generator/src/plugins/utils/jsExpression.ts create mode 100644 packages/code-generator/src/solutions/recore.ts delete mode 100644 packages/code-generator/src/utils/children.ts create mode 100644 packages/code-generator/src/utils/compositeType.ts create mode 100644 packages/code-generator/src/utils/jsExpression.ts create mode 100644 packages/code-generator/src/utils/nodeToJSX.ts create mode 100644 packages/code-generator/src/utils/templateHelper.ts diff --git a/packages/code-generator/package.json b/packages/code-generator/package.json index a368b8f38..808cf1ced 100644 --- a/packages/code-generator/package.json +++ b/packages/code-generator/package.json @@ -9,18 +9,24 @@ "scripts": { "build": "rimraf lib && tsc", "demo": "ts-node -r tsconfig-paths/register ./src/demo/main.ts", + "demo:exp": "ts-node -r tsconfig-paths/register ./src/demo/expressionTest.ts", "test": "ava", "template": "node ./tools/createTemplate.js" }, "dependencies": { "@ali/am-eslint-config": "*", + "@ali/my-prettier": "^1.0.0", + "@babel/generator": "^7.9.5", "@babel/parser": "^7.9.4", + "@babel/traverse": "^7.9.5", + "@babel/types": "^7.9.5", "@types/prettier": "^1.19.1", "change-case": "^3.1.0", "prettier": "^2.0.2", "short-uuid": "^3.1.1" }, "devDependencies": { + "@types/babel__traverse": "^7.0.10", "ava": "^1.0.1", "rimraf": "^3.0.2", "ts-loader": "^6.2.2", diff --git a/packages/code-generator/src/@types/ali__my-prettier/index.d.ts b/packages/code-generator/src/@types/ali__my-prettier/index.d.ts new file mode 100644 index 000000000..d21a2b18a --- /dev/null +++ b/packages/code-generator/src/@types/ali__my-prettier/index.d.ts @@ -0,0 +1,6 @@ +/// + +declare module '@ali/my-prettier' { + function format(text: string, type: string): string; + export default format; +} diff --git a/packages/code-generator/src/const/generator.ts b/packages/code-generator/src/const/generator.ts index 329adfee5..9028b9226 100644 --- a/packages/code-generator/src/const/generator.ts +++ b/packages/code-generator/src/const/generator.ts @@ -8,6 +8,107 @@ export const COMMON_CHUNK_NAME = { StyleDepsImport: 'CommonStyleDepsImport', StyleCssContent: 'CommonStyleCssContent', HtmlContent: 'CommonHtmlContent', + CustomContent: 'CommonCustomContent', }; +export const CLASS_DEFINE_CHUNK_NAME = { + Start: 'CommonClassDefineStart', + ConstructorStart: 'CommonClassDefineConstructorStart', + ConstructorContent: 'CommonClassDefineConstructorContent', + ConstructorEnd: 'CommonClassDefineConstructorEnd', + StaticVar: 'CommonClassDefineStaticVar', + StaticMethod: 'CommonClassDefineStaticMethod', + InsVar: 'CommonClassDefineInsVar', + InsVarMethod: 'CommonClassDefineInsVarMethod', + InsMethod: 'CommonClassDefineInsMethod', + End: 'CommonClassDefineEnd', +}; + +export const DEFAULT_LINK_AFTER = { + [COMMON_CHUNK_NAME.ExternalDepsImport]: [], + [COMMON_CHUNK_NAME.InternalDepsImport]: [COMMON_CHUNK_NAME.ExternalDepsImport], + [COMMON_CHUNK_NAME.FileVarDefine]: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + ], + [COMMON_CHUNK_NAME.FileUtilDefine]: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + ], + [CLASS_DEFINE_CHUNK_NAME.Start]: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + ], + [CLASS_DEFINE_CHUNK_NAME.ConstructorStart]: [ + 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.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.InsVar]: [ + CLASS_DEFINE_CHUNK_NAME.Start, + CLASS_DEFINE_CHUNK_NAME.StaticVar, + CLASS_DEFINE_CHUNK_NAME.StaticMethod, + ], + [CLASS_DEFINE_CHUNK_NAME.InsVarMethod]: [ + CLASS_DEFINE_CHUNK_NAME.Start, + CLASS_DEFINE_CHUNK_NAME.StaticVar, + CLASS_DEFINE_CHUNK_NAME.StaticMethod, + CLASS_DEFINE_CHUNK_NAME.InsVar, + ], + [CLASS_DEFINE_CHUNK_NAME.InsMethod]: [ + 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.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.ConstructorEnd, + ], + [COMMON_CHUNK_NAME.FileMainContent]: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + CLASS_DEFINE_CHUNK_NAME.End, + ], + [COMMON_CHUNK_NAME.FileExport]: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + CLASS_DEFINE_CHUNK_NAME.End, + COMMON_CHUNK_NAME.FileMainContent, + ], + [COMMON_CHUNK_NAME.StyleDepsImport]: [], + [COMMON_CHUNK_NAME.StyleCssContent]: [COMMON_CHUNK_NAME.StyleDepsImport], + [COMMON_CHUNK_NAME.HtmlContent]: [], +} + export const COMMON_SUB_MODULE_NAME = 'index'; diff --git a/packages/code-generator/src/demo/expressionTest.ts b/packages/code-generator/src/demo/expressionTest.ts new file mode 100644 index 000000000..e6d8831ad --- /dev/null +++ b/packages/code-generator/src/demo/expressionTest.ts @@ -0,0 +1,22 @@ +import traverse from '@babel/traverse'; +import * as parser from '@babel/parser'; + +function test(functionBody: string) { + console.log(functionBody); + console.log('---->'); + try { + const parseResult = parser.parse(functionBody); + console.log(JSON.stringify(parseResult)); + traverse(parseResult, { + enter(path) { + console.log('path: ', path.type, path); + } + }); + } catch (error) { + console.log('Error'); + console.log(error.message); + } + console.log('====================='); +} + +test('function main() { state.fff = 1; }'); diff --git a/packages/code-generator/src/generator/ProjectBuilder.ts b/packages/code-generator/src/generator/ProjectBuilder.ts index ada2e47fd..bd36291f6 100644 --- a/packages/code-generator/src/generator/ProjectBuilder.ts +++ b/packages/code-generator/src/generator/ProjectBuilder.ts @@ -90,7 +90,7 @@ export class ProjectBuilder implements IProjectBuilder { const { files } = await builder.generateModule(containerInfo); return { - moduleName: containerInfo.fileName, + moduleName: containerInfo.moduleName, path, files, }; @@ -215,61 +215,19 @@ export class ProjectBuilder implements IProjectBuilder { private createModuleBuilders(): Record { const builders: Record = {}; - builders.components = createModuleBuilder({ - plugins: this.plugins.components, - postProcessors: this.postProcessors, + Object.keys(this.plugins).forEach(pluginName => { + if (this.plugins[pluginName].length > 0) { + const options: { mainFileName?: string } = {}; + if (this.template.slots[pluginName] && this.template.slots[pluginName].fileName) { + options.mainFileName = this.template.slots[pluginName].fileName; + } + builders[pluginName] = createModuleBuilder({ + plugins: this.plugins[pluginName], + postProcessors: this.postProcessors, + ...options, + }); + } }); - builders.pages = createModuleBuilder({ - plugins: this.plugins.pages, - postProcessors: this.postProcessors, - }); - builders.router = createModuleBuilder({ - plugins: this.plugins.router, - mainFileName: this.template.slots.router.fileName, - postProcessors: this.postProcessors, - }); - builders.entry = createModuleBuilder({ - plugins: this.plugins.entry, - mainFileName: this.template.slots.entry.fileName, - postProcessors: this.postProcessors, - }); - builders.globalStyle = createModuleBuilder({ - plugins: this.plugins.globalStyle, - mainFileName: this.template.slots.globalStyle.fileName, - postProcessors: this.postProcessors, - }); - builders.htmlEntry = createModuleBuilder({ - plugins: this.plugins.htmlEntry, - mainFileName: this.template.slots.htmlEntry.fileName, - postProcessors: this.postProcessors, - }); - builders.packageJSON = createModuleBuilder({ - plugins: this.plugins.packageJSON, - mainFileName: this.template.slots.packageJSON.fileName, - postProcessors: this.postProcessors, - }); - - if (this.template.slots.constants && this.plugins.constants) { - builders.constants = createModuleBuilder({ - plugins: this.plugins.constants, - mainFileName: this.template.slots.constants.fileName, - postProcessors: this.postProcessors, - }); - } - if (this.template.slots.utils && this.plugins.utils) { - builders.utils = createModuleBuilder({ - plugins: this.plugins.utils, - mainFileName: this.template.slots.utils.fileName, - postProcessors: this.postProcessors, - }); - } - if (this.template.slots.i18n && this.plugins.i18n) { - builders.i18n = createModuleBuilder({ - plugins: this.plugins.i18n, - mainFileName: this.template.slots.i18n.fileName, - postProcessors: this.postProcessors, - }); - } return builders; } diff --git a/packages/code-generator/src/index.ts b/packages/code-generator/src/index.ts index a0af2ba95..8d2433d25 100644 --- a/packages/code-generator/src/index.ts +++ b/packages/code-generator/src/index.ts @@ -5,6 +5,7 @@ import { createProjectBuilder } from './generator/ProjectBuilder'; import { createDiskPublisher } from './publisher/disk'; import createIceJsProjectBuilder from './solutions/icejs'; +import createRecoreProjectBuilder from './solutions/recore'; export * from './types'; @@ -12,6 +13,7 @@ export default { createProjectBuilder, solutions: { icejs: createIceJsProjectBuilder, + recore: createRecoreProjectBuilder, }, publishers: { disk: createDiskPublisher, diff --git a/packages/code-generator/src/parser/SchemaParser.ts b/packages/code-generator/src/parser/SchemaParser.ts index 4288d894b..a98309a41 100644 --- a/packages/code-generator/src/parser/SchemaParser.ts +++ b/packages/code-generator/src/parser/SchemaParser.ts @@ -5,7 +5,7 @@ import { SUPPORT_SCHEMA_VERSION_LIST } from '../const'; -import { handleChildren } from '../utils/children'; +import { handleChildren } from '../utils/nodeToJSX'; import { ChildNodeType, @@ -28,7 +28,8 @@ import { const defaultContainer: IContainerInfo = { containerType: 'Component', - componentName: 'Index', + componentName: 'Component', + moduleName: 'Index', fileName: 'Index', css: '', props: {}, @@ -78,7 +79,7 @@ class SchemaParser implements ISchemaParser { const container: IContainerInfo = { ...subRoot, containerType: subRoot.componentName, - componentName: subRoot.fileName, + moduleName: subRoot.fileName, // TODO: 驼峰化名称 }; return container; }); @@ -104,9 +105,9 @@ class SchemaParser implements ISchemaParser { const dep: IInternalDependency = { type, - moduleName: container.componentName, + moduleName: container.moduleName, destructuring: false, - exportName: container.componentName, + exportName: container.moduleName, dependencyType: DependencyType.Internal, }; @@ -131,9 +132,15 @@ class SchemaParser implements ISchemaParser { .filter(container => container.containerType === 'Page') .map(page => { const meta = page.meta as IPageMeta; + if (meta) { + return { + path: meta.router, + componentName: page.moduleName, + }; + } return { - path: meta.router, - componentName: page.componentName, + path: '', + componentName: page.moduleName, }; }); diff --git a/packages/code-generator/src/plugins/component/react/const.ts b/packages/code-generator/src/plugins/component/react/const.ts index 0aa8b9c30..629466827 100644 --- a/packages/code-generator/src/plugins/component/react/const.ts +++ b/packages/code-generator/src/plugins/component/react/const.ts @@ -1,15 +1,8 @@ export const REACT_CHUNK_NAME = { - ClassStart: 'ReactComponentClassDefineStart', - ClassEnd: 'ReactComponentClassDefineEnd', - ClassLifeCycle: 'ReactComponentClassMemberLifeCycle', - ClassMethod: 'ReactComponentClassMemberMethod', ClassRenderStart: 'ReactComponentClassRenderStart', ClassRenderPre: 'ReactComponentClassRenderPre', ClassRenderEnd: 'ReactComponentClassRenderEnd', ClassRenderJSX: 'ReactComponentClassRenderJSX', - ClassConstructorStart: 'ReactComponentClassConstructorStart', - ClassConstructorEnd: 'ReactComponentClassConstructorEnd', - ClassConstructorContent: 'ReactComponentClassConstructorContent', ClassDidMountStart: 'ReactComponentClassDidMountStart', ClassDidMountEnd: 'ReactComponentClassDidMountEnd', ClassDidMountContent: 'ReactComponentClassDidMountContent', diff --git a/packages/code-generator/src/plugins/component/react/containerClass.ts b/packages/code-generator/src/plugins/component/react/containerClass.ts index 7cba8c66e..7ca5e685f 100644 --- a/packages/code-generator/src/plugins/component/react/containerClass.ts +++ b/packages/code-generator/src/plugins/component/react/containerClass.ts @@ -1,4 +1,4 @@ -import { COMMON_CHUNK_NAME } from '../../../const/generator'; +import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; import { REACT_CHUNK_NAME } from './const'; import { @@ -21,8 +21,8 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassStart, - content: `class ${ir.componentName} extends React.Component {`, + name: CLASS_DEFINE_CHUNK_NAME.Start, + content: `class ${ir.moduleName} extends React.Component {`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, @@ -34,27 +34,27 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassEnd, + name: CLASS_DEFINE_CHUNK_NAME.End, content: `}`, - linkAfter: [REACT_CHUNK_NAME.ClassStart, REACT_CHUNK_NAME.ClassRenderEnd], + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start, REACT_CHUNK_NAME.ClassRenderEnd], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorStart, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorStart, content: 'constructor(props, context) { super(props); ', - linkAfter: [REACT_CHUNK_NAME.ClassStart], + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorEnd, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, content: '}', linkAfter: [ - REACT_CHUNK_NAME.ClassConstructorStart, - REACT_CHUNK_NAME.ClassConstructorContent, + CLASS_DEFINE_CHUNK_NAME.ConstructorStart, + CLASS_DEFINE_CHUNK_NAME.ConstructorContent, ], }); @@ -64,10 +64,9 @@ const pluginFactory: BuilderComponentPluginFactory = () => { name: REACT_CHUNK_NAME.ClassRenderStart, content: 'render() {', linkAfter: [ - REACT_CHUNK_NAME.ClassStart, - REACT_CHUNK_NAME.ClassConstructorEnd, - REACT_CHUNK_NAME.ClassLifeCycle, - REACT_CHUNK_NAME.ClassMethod, + CLASS_DEFINE_CHUNK_NAME.Start, + CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, + CLASS_DEFINE_CHUNK_NAME.InsMethod, ], }); @@ -87,13 +86,13 @@ const pluginFactory: BuilderComponentPluginFactory = () => { type: ChunkType.STRING, fileType: FileType.JSX, name: COMMON_CHUNK_NAME.FileExport, - content: `export default ${ir.componentName};`, + content: `export default ${ir.moduleName};`, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileUtilDefine, - REACT_CHUNK_NAME.ClassEnd, + 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 74c02e1f4..1f6132b10 100644 --- a/packages/code-generator/src/plugins/component/react/containerDataSource.ts +++ b/packages/code-generator/src/plugins/component/react/containerDataSource.ts @@ -1,6 +1,6 @@ -import { REACT_CHUNK_NAME } from './const'; +import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; -import { generateCompositeType } from '../../utils/compositeType'; +import { generateCompositeType } from '../../../utils/compositeType'; import { BuilderComponentPlugin, @@ -11,7 +11,16 @@ import { IContainerInfo, } from '../../../types'; -const pluginFactory: BuilderComponentPluginFactory = () => { +interface PluginConfig { + fileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, + }; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, @@ -28,10 +37,10 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorContent, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: `this.state = { ${fields.join('')} };`, - linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], + linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], }); } diff --git a/packages/code-generator/src/plugins/component/react/containerInitState.ts b/packages/code-generator/src/plugins/component/react/containerInitState.ts index 74c02e1f4..c3ba77c2b 100644 --- a/packages/code-generator/src/plugins/component/react/containerInitState.ts +++ b/packages/code-generator/src/plugins/component/react/containerInitState.ts @@ -1,6 +1,6 @@ -import { REACT_CHUNK_NAME } from './const'; +import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; -import { generateCompositeType } from '../../utils/compositeType'; +import { generateCompositeType } from '../../../utils/compositeType'; import { BuilderComponentPlugin, @@ -11,7 +11,18 @@ import { IContainerInfo, } from '../../../types'; -const pluginFactory: BuilderComponentPluginFactory = () => { +interface PluginConfig { + fileType: string; + implementType: 'inConstructor' | 'insMember' | 'hooks'; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + implementType: 'inConstructor', + ...config, + }; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, @@ -26,13 +37,23 @@ const pluginFactory: BuilderComponentPluginFactory = () => { return `${stateName}: ${isString ? `'${value}'` : value},`; }); - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorContent, - content: `this.state = { ${fields.join('')} };`, - linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], - }); + 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]], + }); + } } return next; diff --git a/packages/code-generator/src/plugins/component/react/containerInjectUtils.ts b/packages/code-generator/src/plugins/component/react/containerInjectUtils.ts index 8fd70c9ba..bdc6dbd6d 100644 --- a/packages/code-generator/src/plugins/component/react/containerInjectUtils.ts +++ b/packages/code-generator/src/plugins/component/react/containerInjectUtils.ts @@ -1,4 +1,4 @@ -import { REACT_CHUNK_NAME } from './const'; +import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, @@ -8,7 +8,16 @@ import { ICodeStruct, } from '../../../types'; -const pluginFactory: BuilderComponentPluginFactory = () => { +interface PluginConfig { + fileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, + }; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, @@ -16,10 +25,10 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorContent, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: `this.utils = utils;`, - linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], + linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], }); return next; diff --git a/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts b/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts index 39e2a017a..08c88f968 100644 --- a/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts +++ b/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts @@ -1,9 +1,10 @@ +import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; import { REACT_CHUNK_NAME } from './const'; import { getFuncExprBody, transformFuncExpr2MethodMember, -} from '../../utils/jsExpression'; +} from '../../../utils/jsExpression'; import { BuilderComponentPlugin, @@ -17,7 +18,20 @@ import { IJSExpression, } from '../../../types'; -const pluginFactory: BuilderComponentPluginFactory = () => { +interface 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, @@ -28,21 +42,23 @@ const pluginFactory: BuilderComponentPluginFactory = () => { if (ir.lifeCycles) { const lifeCycles = ir.lifeCycles; const chunks = Object.keys(lifeCycles).map(lifeCycleName => { - if (lifeCycleName === 'constructor') { + const normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName; + const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName; + if (normalizeName === 'constructor') { return { type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorContent, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, content: getFuncExprBody( (lifeCycles[lifeCycleName] as IJSExpression).value, ), - linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]], }; } - if (lifeCycleName === 'render') { + if (normalizeName === 'render') { return { type: ChunkType.STRING, - fileType: FileType.JSX, + fileType: cfg.fileType, name: REACT_CHUNK_NAME.ClassRenderPre, content: getFuncExprBody( (lifeCycles[lifeCycleName] as IJSExpression).value, @@ -50,28 +66,17 @@ const pluginFactory: BuilderComponentPluginFactory = () => { linkAfter: [REACT_CHUNK_NAME.ClassRenderStart], }; } - if ( - lifeCycleName === 'componentDidMount' || - lifeCycleName === 'componentDidUpdate' || - lifeCycleName === 'componentWillUnmount' || - lifeCycleName === 'componentDidCatch' - ) { - return { - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassLifeCycle, - content: transformFuncExpr2MethodMember( - lifeCycleName, - (lifeCycles[lifeCycleName] as IJSExpression).value, - ), - linkAfter: [ - REACT_CHUNK_NAME.ClassStart, - REACT_CHUNK_NAME.ClassConstructorEnd, - ], - }; - } - throw new CodeGeneratorError('Unknown life cycle method name'); + return { + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsMethod, + content: transformFuncExpr2MethodMember( + exportName, + (lifeCycles[lifeCycleName] as IJSExpression).value, + ), + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], + }; }); next.chunks.push.apply(next.chunks, chunks); diff --git a/packages/code-generator/src/plugins/component/react/containerMethod.ts b/packages/code-generator/src/plugins/component/react/containerMethod.ts index 1f141dbf6..527aad815 100644 --- a/packages/code-generator/src/plugins/component/react/containerMethod.ts +++ b/packages/code-generator/src/plugins/component/react/containerMethod.ts @@ -1,6 +1,6 @@ -import { REACT_CHUNK_NAME } from './const'; +import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; -import { transformFuncExpr2MethodMember } from '../../utils/jsExpression'; +import { transformFuncExpr2MethodMember } from '../../../utils/jsExpression'; import { BuilderComponentPlugin, @@ -13,7 +13,16 @@ import { IJSExpression, } from '../../../types'; -const pluginFactory: BuilderComponentPluginFactory = () => { +interface PluginConfig { + fileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, + }; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, @@ -25,17 +34,13 @@ const pluginFactory: BuilderComponentPluginFactory = () => { const methods = ir.methods; const chunks = Object.keys(methods).map(methodName => ({ type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassMethod, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: transformFuncExpr2MethodMember( methodName, (methods[methodName] as IJSExpression).value, ), - linkAfter: [ - REACT_CHUNK_NAME.ClassStart, - REACT_CHUNK_NAME.ClassConstructorEnd, - REACT_CHUNK_NAME.ClassLifeCycle, - ], + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], })); next.chunks.push.apply(next.chunks, chunks); diff --git a/packages/code-generator/src/plugins/component/react/jsx.ts b/packages/code-generator/src/plugins/component/react/jsx.ts index e1b5d2306..2b168d259 100644 --- a/packages/code-generator/src/plugins/component/react/jsx.ts +++ b/packages/code-generator/src/plugins/component/react/jsx.ts @@ -1,142 +1,39 @@ import { BuilderComponentPlugin, BuilderComponentPluginFactory, - ChildNodeItem, - ChildNodeType, ChunkType, FileType, ICodeStruct, - IComponentNodeItem, IContainerInfo, - IInlineStyle, - IJSExpression, } from '../../../types'; -import { handleChildren } from '../../../utils/children'; -import { generateCompositeType } from '../../utils/compositeType'; import { REACT_CHUNK_NAME } from './const'; -function generateInlineStyle(style: IInlineStyle): string | null { - const attrLines = Object.keys(style).map((cssAttribute: string) => { - const [isString, valueStr] = generateCompositeType(style[cssAttribute]); - const valuePart = isString ? `'${valueStr}'` : valueStr; - return `${cssAttribute}: ${valuePart},`; - }); +import { createReactNodeGenerator } from '../../../utils/nodeToJSX'; - if (attrLines.length === 0) { - return null; - } - - return `{ ${attrLines.join('')} }`; +interface PluginConfig { + fileType: string; } -function generateAttr(attrName: string, attrValue: any): string { - if (attrName === 'initValue' || attrName === 'labelCol') { - return ''; - } - const [isString, valueStr] = generateCompositeType(attrValue); - return `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`; -} +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, + }; -function mapNodeName(src: string): string { - if (src === 'Div') { - return 'div'; - } - return src; -} + const generator = createReactNodeGenerator(); -function generateNode(nodeItem: IComponentNodeItem): string { - const codePieces: string[] = []; - let propLines: string[] = []; - const { className, style, ...props } = nodeItem.props; - - codePieces.push(`<${mapNodeName(nodeItem.componentName)}`); - if (className) { - propLines.push(`className="${className}"`); - } - if (style) { - const inlineStyle = generateInlineStyle(style); - if (inlineStyle !== null) { - propLines.push(`style={${inlineStyle}}`); - } - } - - propLines = propLines.concat( - Object.keys(props).map((propName: string) => - generateAttr(propName, props[propName]), - ), - ); - codePieces.push(` ${propLines.join(' ')} `); - - if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) { - codePieces.push('>'); - const childrenLines = generateChildren(nodeItem.children); - codePieces.push.apply(codePieces, childrenLines); - codePieces.push(``); - } else { - codePieces.push('/>'); - } - - if (nodeItem.loop && nodeItem.loopArgs) { - let loopDataExp; - if ((nodeItem.loop as IJSExpression).type === 'JSExpression') { - loopDataExp = `(${(nodeItem.loop as IJSExpression).value})`; - } else { - loopDataExp = JSON.stringify(nodeItem.loop); - } - codePieces.unshift( - `${loopDataExp}.map((${nodeItem.loopArgs[0]}, ${nodeItem.loopArgs[1]}) => (`, - ); - codePieces.push('))'); - } - - if (nodeItem.condition) { - codePieces.unshift(`(${generateCompositeType(nodeItem.condition)}) && (`); - codePieces.push(')'); - } - - if (nodeItem.condition || (nodeItem.loop && nodeItem.loopArgs)) { - codePieces.unshift('{'); - codePieces.push('}'); - } - - return codePieces.join(''); -} - -function generateChildren(children: ChildNodeType): string[] { - return handleChildren(children, { - // TODO: 如果容器直接只有一个 字符串 children 呢? - string: (input: string) => [input], - expression: (input: IJSExpression) => [`{${input.value}}`], - node: (input: IComponentNodeItem) => [generateNode(input)], - }); -} - -const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, }; const ir = next.ir as IContainerInfo; - - let jsxContent: string; - if (!ir.children || (ir.children as unknown[]).length === 0) { - jsxContent = 'null'; - } else { - const childrenCode = generateChildren(ir.children); - if (childrenCode.length === 1) { - jsxContent = `(${childrenCode[0]})`; - } else { - jsxContent = `(${childrenCode.join( - '', - )})`; - } - } + const jsxContent = generator(ir); next.chunks.push({ type: ChunkType.STRING, - fileType: FileType.JSX, + fileType: cfg.fileType, name: REACT_CHUNK_NAME.ClassRenderJSX, content: `return ${jsxContent};`, linkAfter: [ diff --git a/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts b/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts index 434f44564..2e75a5b7e 100644 --- a/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts +++ b/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts @@ -9,7 +9,6 @@ import { IContainerInfo, } from '../../../types'; -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { diff --git a/packages/code-generator/src/plugins/component/recore/const.ts b/packages/code-generator/src/plugins/component/recore/const.ts new file mode 100644 index 000000000..b848f42aa --- /dev/null +++ b/packages/code-generator/src/plugins/component/recore/const.ts @@ -0,0 +1,3 @@ +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 new file mode 100644 index 000000000..da7de385e --- /dev/null +++ b/packages/code-generator/src/plugins/component/recore/pageDataSource.ts @@ -0,0 +1,73 @@ +import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + FileType, + ICodeStruct, + IContainerInfo, + IJSExpression, + CompositeValue, +} from '../../../types'; + +import { generateCompositeType, handleStringValueDefault } from '../../../utils/compositeType'; +import { generateExpression } from '../../../utils/jsExpression'; + +function packJsExpression(exp: unknown): string { + const expression = exp as IJSExpression; + const funcStr = generateExpression(expression); + return `function() { return (${funcStr}); }`; +} + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IContainerInfo; + if (ir.dataSource) { + const { dataSource } = ir; + const { + list, + dataHandler, + ...rest + } = dataSource; + + let attrs: string[] = []; + + const extConfigs = Object.keys(rest).map(extConfigName => { + const value = rest[extConfigName] as CompositeValue; + const [isString, valueStr] = generateCompositeType(value); + return `${extConfigName}: ${isString ? `'${valueStr}'` : valueStr}`; + }); + + attrs = [...attrs, ...extConfigs]; + + if (dataHandler) { + const handlerContent = packJsExpression(dataHandler); + attrs.push(`dataHandler: ${handlerContent}`); + } + + const listProp = handleStringValueDefault(generateCompositeType(list as unknown as CompositeValue, { + expression: packJsExpression, + })); + + attrs.push(`list: ${listProp}`); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: `dataSourceOptions = { ${attrs.join(',\n')} };`, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], + }); + } + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/recore/pageFrame.ts b/packages/code-generator/src/plugins/component/recore/pageFrame.ts new file mode 100644 index 000000000..e302a64bc --- /dev/null +++ b/packages/code-generator/src/plugins/component/recore/pageFrame.ts @@ -0,0 +1,81 @@ +import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; + +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; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: COMMON_CHUNK_NAME.ExternalDepsImport, + content: `import { BaseController } from '@ali/recore-renderer';`, + linkAfter: [], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: CLASS_DEFINE_CHUNK_NAME.Start, + content: `class ${ir.moduleName} extends BaseController {`, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.Start]], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: CLASS_DEFINE_CHUNK_NAME.End, + content: `}`, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.End]], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorStart, + content: 'init() {', + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, + content: '}', + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorEnd]], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: 'globalProps = (window as any)?.g_config?.globalProps || {};', + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: COMMON_CHUNK_NAME.FileExport, + content: `export default ${ir.moduleName};`, + linkAfter: [...DEFAULT_LINK_AFTER[COMMON_CHUNK_NAME.FileExport]], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/recore/pageStyle.ts b/packages/code-generator/src/plugins/component/recore/pageStyle.ts new file mode 100644 index 000000000..fc8ca41b5 --- /dev/null +++ b/packages/code-generator/src/plugins/component/recore/pageStyle.ts @@ -0,0 +1,35 @@ +import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + ICodeStruct, + FileType, + IContainerInfo, +} from '../../../types'; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IContainerInfo; + + if (ir.css) { + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.TS, + name: CLASS_DEFINE_CHUNK_NAME.StaticVar, + content: `static cssText = '${ir.css.replace(/\'/g, '\\\'')}';`, + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.StaticVar]], + }); + } + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/recore/pageVmBody.ts b/packages/code-generator/src/plugins/component/recore/pageVmBody.ts new file mode 100644 index 000000000..8660ad195 --- /dev/null +++ b/packages/code-generator/src/plugins/component/recore/pageVmBody.ts @@ -0,0 +1,82 @@ +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + ICodeStruct, + IContainerInfo, + IComponentNodeItem, + CodePiece, + PIECE_TYPE, +} from '../../../types'; +import { COMMON_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator'; + +import { createNodeGenerator, generateString } from '../../../utils/nodeToJSX'; +import { generateExpression } from '../../../utils/jsExpression'; +import { generateCompositeType, handleStringValueDefault } from '../../../utils/compositeType'; + +const generateGlobalProps = (nodeItem: IComponentNodeItem): CodePiece[] => { + return [{ + value: `{...globalProps.${nodeItem.componentName}}`, + type: PIECE_TYPE.ATTR, + }]; +}; + +const generateCtrlLine = (nodeItem: IComponentNodeItem): CodePiece[] => { + const pieces: CodePiece[] = []; + + if (nodeItem.loop && nodeItem.loopArgs) { + const loopDataExp = handleStringValueDefault(generateCompositeType(nodeItem.loop)); + pieces.push({ + type: PIECE_TYPE.ATTR, + value: `x-for={${loopDataExp}}`, + }); + + pieces.push({ + type: PIECE_TYPE.ATTR, + value: `x-each="${nodeItem.loopArgs[0]},${nodeItem.loopArgs[1]}"`, + }); + } + + if (nodeItem.condition) { + const conditionExp = handleStringValueDefault(generateCompositeType(nodeItem.condition)); + pieces.push({ + type: PIECE_TYPE.ATTR, + value: `x-if={${conditionExp}}`, + }); + } + + return pieces; +}; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const generator = createNodeGenerator({ + string: generateString, + expression: (input) => [generateExpression(input)], + }, [ + generateGlobalProps, + generateCtrlLine, + ]); + + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IContainerInfo; + + const vxContent = generator(ir); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: 'vx', + name: COMMON_CHUNK_NAME.CustomContent, + content: vxContent, + linkAfter: [], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/recore/pageVmHeader.ts b/packages/code-generator/src/plugins/component/recore/pageVmHeader.ts new file mode 100644 index 000000000..6ef8ac44f --- /dev/null +++ b/packages/code-generator/src/plugins/component/recore/pageVmHeader.ts @@ -0,0 +1,29 @@ +import { COMMON_CHUNK_NAME } from '../../../const/generator'; + +import { + BuilderComponentPlugin, + BuilderComponentPluginFactory, + ChunkType, + ICodeStruct, +} from '../../../types'; + +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: 'vx', + name: COMMON_CHUNK_NAME.CustomContent, + content: `
`, + linkAfter: [], + }); + + return next; + }; + return plugin; +}; + +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/style/css.ts b/packages/code-generator/src/plugins/component/style/css.ts index c803d24a9..cb9b2a4e0 100644 --- a/packages/code-generator/src/plugins/component/style/css.ts +++ b/packages/code-generator/src/plugins/component/style/css.ts @@ -9,7 +9,18 @@ import { IContainerInfo, } from '../../../types'; -const pluginFactory: BuilderComponentPluginFactory = () => { +interface PluginConfig { + fileType: string; + moduleFileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.CSS, + moduleFileType: FileType.JSX, + ...config, + }; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, @@ -19,7 +30,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, - fileType: FileType.CSS, + fileType: cfg.fileType, name: COMMON_CHUNK_NAME.StyleCssContent, content: ir.css, linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], @@ -27,9 +38,9 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, - fileType: FileType.JSX, + fileType: cfg.moduleFileType, name: COMMON_CHUNK_NAME.InternalDepsImport, - content: `import './index.css';`, + content: `import './index.${cfg.fileType}';`, linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); diff --git a/packages/code-generator/src/plugins/project/constants.ts b/packages/code-generator/src/plugins/project/constants.ts index 8c1302cd0..5c02e9711 100644 --- a/packages/code-generator/src/plugins/project/constants.ts +++ b/packages/code-generator/src/plugins/project/constants.ts @@ -1,5 +1,5 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; -import { generateCompositeType } from '../../plugins/utils/compositeType'; +import { generateCompositeType } from '../../utils/compositeType'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, @@ -9,7 +9,6 @@ import { IProjectInfo, } from '../../types'; -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { 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 28bef8f0a..0cf3918a7 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 @@ -9,7 +9,6 @@ import { IProjectInfo, } from '../../../../../types'; -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { 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 644e6155d..a0ca3cdf1 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 @@ -9,7 +9,6 @@ import { IProjectInfo, } from '../../../../../types'; -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/globalStyle.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/globalStyle.ts index 92ebf8730..3daaeecdc 100644 --- a/packages/code-generator/src/plugins/project/framework/icejs/plugins/globalStyle.ts +++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/globalStyle.ts @@ -9,7 +9,6 @@ import { IProjectInfo, } from '../../../../../types'; -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { 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 f00381a0b..a79dc3481 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 @@ -21,7 +21,6 @@ interface IIceJsPackageJSON extends IPackageJSON { originTemplate: string; } -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { 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 9dc436b28..b4f2d718f 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 @@ -9,7 +9,6 @@ import { IRouterInfo, } from '../../../../../types'; -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { 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 2a1f3a285..d048bd389 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 @@ -2,8 +2,8 @@ import ResultDir from '../../../../../model/ResultDir'; import { IProjectTemplate, IResultDir, - IResultFile, } from '../../../../../types'; +import { runFileGenerator } from '../../../../../utils/templateHelper'; import file12 from './files/abc.json'; import file11 from './files/build.json'; @@ -26,29 +26,6 @@ import file3 from './files/stylelintignore'; import file2 from './files/stylelintrc.js'; import file1 from './files/tsconfig.json'; -type FuncFileGenerator = () => [string[], IResultFile]; - -function insertFile(root: IResultDir, path: string[], file: IResultFile) { - let current: IResultDir = root; - path.forEach(pathNode => { - const dir = current.dirs.find(d => d.name === pathNode); - if (dir) { - current = dir; - } else { - const newDir = new ResultDir(pathNode); - current.addDirectory(newDir); - current = newDir; - } - }); - - current.addFile(file); -} - -function runFileGenerator(root: IResultDir, fun: FuncFileGenerator) { - const [path, file] = fun(); - insertFile(root, path, file); -} - const icejsTemplate: IProjectTemplate = { slots: { components: { diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/README.md.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/README.md.ts index 2c01ceaef..f90fd7d87 100644 --- a/packages/code-generator/src/plugins/project/framework/recore/template/files/README.md.ts +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/README.md.ts @@ -14,7 +14,7 @@ export default function getFile(): [string[], IResultFile] { ## 安装运行 -```bash +\`\`\`bash # install dependencies tnpm install @@ -26,11 +26,10 @@ npm test # local build npm run build -``` +\`\`\` `, ); return [[], file]; } - \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/package.json.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/package.json.ts index c321542ab..d8da2daaa 100644 --- a/packages/code-generator/src/plugins/project/framework/recore/template/files/package.json.ts +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/package.json.ts @@ -21,41 +21,40 @@ export default function getFile(): [string[], IResultFile] { "npm": ">=6.1.0" }, "dependencies": { - "@ali/recore": "^1.6.10", "@ali/lowcode-runtime": "^0.8.0", - "@ali/recore-renderer": "^0.0.1", - "@ali/vc-shell": "1.3.1", + "@ali/recore": "^1.6.10", + "@ali/recore-renderer": "^0.0.3", "@ali/vc-block": "^3.0.3-beta.1", "@ali/vc-deep": "1.2.38", "@ali/vc-div": "^1.0.1", "@ali/vc-page": "^1.0.5", + "@ali/vc-shell": "1.3.1", "@ali/vc-slot": "^2.0.1", "@ali/vc-text": "^4.0.1", "@ali/vu-dataSource": "^1.0.4", "@ali/vu-formatter": "^2.0.0", "@ali/vu-fusion": "^2.0.1-beta.0", "@ali/vu-legao-builtin": "^1.4.0-beta.2", + "@ali/vu-toolkit": "^1.0.5", "react": "^16" }, "devDependencies": { - "build-plugin-react-app": "^1.0.15", - "@ali/build-plugin-recore-lowcode": "^0.0.1", + "@ali/build-plugin-recore-lowcode": "^0.0.3", + "@ali/recore-lowcode-loader": "^0.0.4", "@alib/build-scripts": "^0.1.0", "@types/node": "^7", "@types/react": "^16", + "build-plugin-react-app": "^1.0.15", "eslint": "^6.5.1", + "prettier": "^1.18.2", "tslib": "^1.9.3", - "typescript": "^3.1.3", - "prettier": "^1.18.2" + "typescript": "^3.1.3" }, "lint-staged": { "./src/**/*.{ts,tsx}": [ "tslint --fix", "git add" ] - }, - "nowa": { - "solution": "@ali/nowa-recore-solution" } } diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/index.ts.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/index.ts.ts index 8fdb5c211..979b85a20 100644 --- a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/index.ts.ts +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/index.ts.ts @@ -8,8 +8,81 @@ export default function getFile(): [string[], IResultFile] { 'ts', ` import { app } from '@ali/lowcode-runtime'; +import { ReactProvider } from '@ali/lowcode-runtime'; import Shell from '@ali/vc-shell'; import StaticRender from './plugins/provider'; +import Router from '@/router'; +import appConfig from '@/config/app'; +import components from '@/config/components'; +import utils from '@/config/utils'; + +// 定制加载应用配置的逻辑 +class StaticRender extends ReactProvider { + // 初始化时调用,如可以在这里注入全局API + init() { + const gConfig = (window as any).g_config || {}; + const LeGao = { + __ctx__: {}, + createContext: (cfg: any) => { + const { schema } = cfg || {}; + // 1. 根据参数拉取schema + if (schema && typeof schema === 'string') { + this.setHomePage(schema); + } + const { isSectionalRender, autoRender } = gConfig || {}; + if (isSectionalRender && !autoRender) { + // 2. 渲染 + this.setSectionalRender(); + this.ready(); + } + const provider = this; + class Context { + get utils() { + return provider.getUtils(); + } + get components() { + return provider.getComponents(); + } + } + const ctx = new Context(); + (LeGao.__ctx__ as any)[this.getContainerId()] = ctx; + return ctx; + }, + getContext: (id: string) => { + if (!id) { + for (id in LeGao.__ctx__) { + return (LeGao.__ctx__ as any)[id]; + } + } + return (LeGao.__ctx__ as any)[id]; + } + }; + (window as any).LeGao = LeGao; + if (gConfig.index) { + this.setHomePage(gConfig.index); + } + if (gConfig.isSectionalRender) { + this.setSectionalRender(); + if (!gConfig.autoRender) { + return; + } + } + this.ready(); + } + + // 定制获取、处理应用配置(组件、插件、路由模式、布局等)的逻辑 + getAppData() { + return { + ...appConfig, + components, + utils: utils, + } + } + + getRouterView() { + return Router; + } +} // 注册布局组件,可注册多个 app.registerLayout(Shell, { diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/plugins/provider.ts.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/plugins/provider.ts.ts deleted file mode 100644 index 8d20e365d..000000000 --- a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/plugins/provider.ts.ts +++ /dev/null @@ -1,89 +0,0 @@ - -import ResultFile from '../../../../../../../../model/ResultFile'; -import { IResultFile } from '../../../../../../../../types'; - -export default function getFile(): [string[], IResultFile] { - const file = new ResultFile( - 'provider', - 'ts', - ` -import { ReactProvider } from '@ali/lowcode-runtime'; -import Router from '@/router'; -import appConfig from '@/config/app'; -import components from '@/config/components'; -import utils from '@/config/utils'; - -// 定制加载应用配置的逻辑 -export default class MyProvider extends ReactProvider { - // 初始化时调用,如可以在这里注入全局API - init() { - const gConfig = (window as any).g_config || {}; - const LeGao = { - __ctx__: {}, - createContext: (cfg: any) => { - const { schema } = cfg || {}; - // 1. 根据参数拉取schema - if (schema && typeof schema === 'string') { - this.setHomePage(schema); - } - const { isSectionalRender, autoRender } = gConfig || {}; - if (isSectionalRender && !autoRender) { - // 2. 渲染 - this.setSectionalRender(); - this.ready(); - } - const provider = this; - class Context { - get utils() { - return provider.getUtils(); - } - get components() { - return provider.getComponents(); - } - } - const ctx = new Context(); - (LeGao.__ctx__ as any)[this.getContainerId()] = ctx; - return ctx; - }, - getContext: (id: string) => { - if (!id) { - for (id in LeGao.__ctx__) { - return (LeGao.__ctx__ as any)[id]; - } - } - return (LeGao.__ctx__ as any)[id]; - } - }; - (window as any).LeGao = LeGao; - if (gConfig.index) { - this.setHomePage(gConfig.index); - } - if (gConfig.isSectionalRender) { - this.setSectionalRender(); - if (!gConfig.autoRender) { - return; - } - } - this.ready(); - } - - // 定制获取、处理应用配置(组件、插件、路由模式、布局等)的逻辑 - getAppData() { - return { - ...appConfig, - components, - utils: utils, - } - } - - getRouterView() { - return Router; - } -} - - `, - ); - - return [['src','plugins'], file]; -} - \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/router.ts.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/router.ts.ts index 4dd0e3124..78d1fff61 100644 --- a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/router.ts.ts +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/router.ts.ts @@ -11,7 +11,7 @@ export default { baseDir: './pages', exact: true, routes: [ - { main: './index', path: '/' }, + { main: './page_index', path: '/' }, ], }; `, @@ -19,4 +19,3 @@ export default { return [['src'], file]; } - \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/index.ts b/packages/code-generator/src/plugins/project/framework/recore/template/index.ts new file mode 100644 index 000000000..1fd9565b9 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/index.ts @@ -0,0 +1,54 @@ +import ResultDir from '../../../../../model/ResultDir'; +import { + IProjectTemplate, + IResultDir, +} from '../../../../../types'; +import { runFileGenerator } from '../../../../../utils/templateHelper'; + +import file1 from './files/abc.json'; +import file2 from './files/build.json'; +import file3 from './files/.editorconfig'; +import file4 from './files/.eslintignore'; +import file5 from './files/.gitignore'; +import file6 from './files/.prettierrc'; +import file7 from './files/README.md'; +import file8 from './files/package.json'; +import file9 from './files/public/index.html'; +import file10 from './files/src/index.ts'; +import file11 from './files/src/router.ts'; +import file13 from './files/src/config/app.ts'; +import file14 from './files/src/config/components.ts'; +import file15 from './files/src/config/utils.ts'; +import file16 from './files/tsconfig.json'; + +const icejsTemplate: IProjectTemplate = { + slots: { + pages: { + path: ['src', 'pages'], + }, + }, + + generateTemplate(): IResultDir { + const root = new ResultDir('.'); + + runFileGenerator(root, file1); + runFileGenerator(root, file2); + runFileGenerator(root, file3); + runFileGenerator(root, file4); + runFileGenerator(root, file5); + runFileGenerator(root, file6); + runFileGenerator(root, file7); + runFileGenerator(root, file8); + runFileGenerator(root, file9); + runFileGenerator(root, file10); + runFileGenerator(root, file11); + runFileGenerator(root, file13); + runFileGenerator(root, file14); + runFileGenerator(root, file15); + runFileGenerator(root, file16); + + return root; + }, +}; + +export default icejsTemplate; diff --git a/packages/code-generator/src/plugins/project/i18n.ts b/packages/code-generator/src/plugins/project/i18n.ts index bb3623032..cada1fd96 100644 --- a/packages/code-generator/src/plugins/project/i18n.ts +++ b/packages/code-generator/src/plugins/project/i18n.ts @@ -1,5 +1,5 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; -import { generateCompositeType } from '../../plugins/utils/compositeType'; +import { generateCompositeType } from '../../utils/compositeType'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, @@ -9,7 +9,6 @@ import { IProjectInfo, } from '../../types'; -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { diff --git a/packages/code-generator/src/plugins/project/utils.ts b/packages/code-generator/src/plugins/project/utils.ts index 1f6f0a152..32e7c5d17 100644 --- a/packages/code-generator/src/plugins/project/utils.ts +++ b/packages/code-generator/src/plugins/project/utils.ts @@ -9,7 +9,6 @@ import { IUtilInfo, } from '../../types'; -// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory = () => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { diff --git a/packages/code-generator/src/plugins/utils/compositeType.ts b/packages/code-generator/src/plugins/utils/compositeType.ts deleted file mode 100644 index 2e102fcac..000000000 --- a/packages/code-generator/src/plugins/utils/compositeType.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { CompositeArray, CompositeValue, ICompositeObject } from '../../types'; -import { generateValue, isJsExpression } from './jsExpression'; - -function generateArray(value: CompositeArray): string { - const body = value.map(v => generateUnknownType(v)).join(','); - return `[${body}]`; -} - -function generateObject(value: ICompositeObject): string { - if (isJsExpression(value)) { - return generateValue(value); - } - - const body = Object.keys(value) - .map(key => { - const v = generateUnknownType(value[key]); - return `${key}: ${v}`; - }) - .join(','); - - return `{${body}}`; -} - -function generateUnknownType(value: CompositeValue): string { - if (Array.isArray(value)) { - return generateArray(value as CompositeArray); - } else if (typeof value === 'object') { - return generateObject(value as ICompositeObject); - } else if (typeof value === 'string') { - return `'${value}'`; - } - return `${value}`; -} - -export function generateCompositeType( - value: CompositeValue, -): [boolean, string] { - const result = generateUnknownType(value); - - if (result.substr(0, 1) === "'" && result.substr(-1, 1) === "'") { - return [true, result.substring(1, result.length - 1)]; - } - - return [false, result]; -} diff --git a/packages/code-generator/src/plugins/utils/jsExpression.ts b/packages/code-generator/src/plugins/utils/jsExpression.ts deleted file mode 100644 index eda1883bc..000000000 --- a/packages/code-generator/src/plugins/utils/jsExpression.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { CodeGeneratorError, IJSExpression } from '../../types'; - -export function transformFuncExpr2MethodMember( - methodName: string, - functionBody: string, -): string { - if (functionBody.indexOf('function') < 8) { - return functionBody.replace('function', methodName); - } - return functionBody; -} - -export function getFuncExprBody(functionBody: string) { - const start = functionBody.indexOf('{'); - const end = functionBody.lastIndexOf('}'); - - if (start < 0 || end < 0 || end < start) { - throw new CodeGeneratorError('JSExpression has no valid body.'); - } - - const body = functionBody.slice(start + 1, end); - return body; -} - -export function generateValue(value: any): string { - if (value && (value as IJSExpression).type === 'JSExpression') { - return (value as IJSExpression).value; - } - - throw new CodeGeneratorError('Not a JSExpression'); -} - -export function isJsExpression(value: any): boolean { - return ( - value && - typeof value === 'object' && - (value as IJSExpression).type === 'JSExpression' - ); -} diff --git a/packages/code-generator/src/postprocessor/prettier/index.ts b/packages/code-generator/src/postprocessor/prettier/index.ts index f36009fad..519c2db5d 100644 --- a/packages/code-generator/src/postprocessor/prettier/index.ts +++ b/packages/code-generator/src/postprocessor/prettier/index.ts @@ -1,16 +1,32 @@ import prettier from 'prettier'; +import mypretter from '@ali/my-prettier'; import { PostProcessor, PostProcessorFactory } from '../../types'; const PARSERS = ['css', 'scss', 'less', 'json', 'html', 'vue']; -const factory: PostProcessorFactory = () => { +interface ProcessorConfig { + customFileTypeParser: Record; +} + +const factory: PostProcessorFactory = (config?: ProcessorConfig) => { + const cfg: ProcessorConfig = { + customFileTypeParser: {}, + ...config, + }; + const codePrettier: PostProcessor = (content: string, fileType: string) => { let parser: prettier.BuiltInParserName; - if (fileType === 'js' || fileType === 'jsx' || fileType === 'ts' || fileType === 'tsx') { + if (fileType === 'js' || fileType === 'jsx') { parser = 'babel'; + } else if (fileType === 'ts' || fileType === 'tsx') { + parser = 'typescript'; } else if (PARSERS.indexOf(fileType) >= 0) { parser = fileType as prettier.BuiltInParserName; + } else if (cfg.customFileTypeParser[fileType]){ + parser = cfg.customFileTypeParser[fileType] as prettier.BuiltInParserName; + } else if (fileType === 'vx') { + return mypretter(content, fileType); } else { return content; } diff --git a/packages/code-generator/src/solutions/recore.ts b/packages/code-generator/src/solutions/recore.ts new file mode 100644 index 000000000..9850f2b3a --- /dev/null +++ b/packages/code-generator/src/solutions/recore.ts @@ -0,0 +1,51 @@ +import { IProjectBuilder } from '../types'; + +import { createProjectBuilder } from '../generator/ProjectBuilder'; + +// import esmodule from '../plugins/common/esmodule'; +import containerInitState from '../plugins/component/react/containerInitState'; +import containerLifeCycle from '../plugins/component/react/containerLifeCycle'; +import containerMethod from '../plugins/component/react/containerMethod'; +import pageFrame from '../plugins/component/recore/pageFrame'; +import pageStyle from '../plugins/component/recore/pageStyle'; +import pageVmHeader from '../plugins/component/recore/pageVmHeader'; +import pageVmBody from '../plugins/component/recore/pageVmBody'; +import pageDataSource from '../plugins/component/recore/pageDataSource'; +import template from '../plugins/project/framework/recore/template'; + +import { prettier } from '../postprocessor'; + +export default function createRecoreProjectBuilder(): IProjectBuilder { + return createProjectBuilder({ + template, + plugins: { + pages: [ + pageFrame(), + pageStyle(), + containerInitState({ + fileType: 'ts', + implementType: 'insMember', + }), + containerLifeCycle({ + fileType: 'ts', + exportNameMapping: { + constructor: 'init', + componentDidMount: 'didMount', + willUnmount: 'willUnMount', + componentWillUnmount: 'willUnMount', + }, + normalizeNameMapping: { + init: 'constructor', + }, + }), + containerMethod({ + fileType: 'ts', + }), + pageDataSource(), + pageVmHeader(), + pageVmBody(), + ], + }, + postProcessors: [prettier()], + }); +} diff --git a/packages/code-generator/src/types/core.ts b/packages/code-generator/src/types/core.ts index 750d41395..5cdffaa4b 100644 --- a/packages/code-generator/src/types/core.ts +++ b/packages/code-generator/src/types/core.ts @@ -4,6 +4,8 @@ import { IProjectSchema, IResultDir, IResultFile, + IComponentNodeItem, + IJSExpression, } from './index'; export enum FileType { @@ -107,7 +109,7 @@ export interface ISchemaParser { } export interface IProjectTemplate { - slots: IProjectSlots; + slots: Record; generateTemplate(): IResultDir; } @@ -116,30 +118,21 @@ export interface IProjectSlot { fileName?: string; } -export interface IProjectSlots { - components: IProjectSlot; - pages: IProjectSlot; - router: IProjectSlot; - entry: IProjectSlot; - constants?: IProjectSlot; - utils?: IProjectSlot; - i18n?: IProjectSlot; - globalStyle: IProjectSlot; - htmlEntry: IProjectSlot; - packageJSON: IProjectSlot; -} +// export interface IProjectSlots { +// components: IProjectSlot; +// pages: IProjectSlot; +// router: IProjectSlot; +// entry: IProjectSlot; +// constants?: IProjectSlot; +// utils?: IProjectSlot; +// i18n?: IProjectSlot; +// globalStyle: IProjectSlot; +// htmlEntry: IProjectSlot; +// packageJSON: IProjectSlot; +// } export interface IProjectPlugins { - components: BuilderComponentPlugin[]; - pages: BuilderComponentPlugin[]; - router: BuilderComponentPlugin[]; - entry: BuilderComponentPlugin[]; - constants?: BuilderComponentPlugin[]; - utils?: BuilderComponentPlugin[]; - i18n?: BuilderComponentPlugin[]; - globalStyle: BuilderComponentPlugin[]; - htmlEntry: BuilderComponentPlugin[]; - packageJSON: BuilderComponentPlugin[]; + [slotName: string]: BuilderComponentPlugin[]; } export interface IProjectBuilder { @@ -153,3 +146,25 @@ export type PostProcessor = (content: string, fileType: string) => string; export interface IPluginOptions { fileDirDepth: number; } + +export enum PIECE_TYPE { + BEFORE = 'NodeCodePieceBefore', + TAG = 'NodeCodePieceTag', + ATTR = 'NodeCodePieceAttr', + CHILDREN = 'NodeCodePieceChildren', + AFTER = 'NodeCodePieceAfter', +}; + +export interface CodePiece { + value: string; + type: PIECE_TYPE; +} + +export interface HandlerSet { + string?: (input: string) => T[]; + expression?: (input: IJSExpression) => T[]; + node?: (input: IComponentNodeItem) => T[]; + common?: (input: unknown) => T[]; +} + +export type ExtGeneratorPlugin = (nodeItem: IComponentNodeItem) => CodePiece[]; diff --git a/packages/code-generator/src/types/intermediate.ts b/packages/code-generator/src/types/intermediate.ts index 7ac66079a..54142dfa0 100644 --- a/packages/code-generator/src/types/intermediate.ts +++ b/packages/code-generator/src/types/intermediate.ts @@ -17,8 +17,8 @@ export interface IParseResult { } export interface IContainerInfo extends IContainerNodeItem, IWithDependency { - componentName: string; containerType: string; + moduleName: string; } export interface IWithDependency { diff --git a/packages/code-generator/src/types/schema.ts b/packages/code-generator/src/types/schema.ts index 343e6442d..aa6e9459c 100644 --- a/packages/code-generator/src/types/schema.ts +++ b/packages/code-generator/src/types/schema.ts @@ -10,6 +10,7 @@ import { IExternalDependency } from './index'; export interface IJSExpression { type: 'JSExpression'; value: string; + [extConfigName: string]: any; } // JSON 基本类型 @@ -124,7 +125,7 @@ export interface IComponentNodeItem { * @extends {IComponentNodeItem} */ export interface IContainerNodeItem extends IComponentNodeItem { - componentName: string; // 'Page' | 'Block' | 'Component' 组件类型 必填、首字母大写 + componentName: 'Page' | 'Block' | 'Component'; // 'Page' | 'Block' | 'Component' 组件类型 必填、首字母大写 fileName: string; // 文件名称 必填、英文 defaultProps?: { [propName: string]: any; // 业务属性 @@ -166,6 +167,7 @@ export interface IDataSource { * 支持返回一个Promise,通过resolve(返回数据),常用于串型发送请求场景,配合this.dataSourceMap[oneRequest.id].load()使用; */ dataHandler?: IJSExpression; + [extConfigName: string]: any; } /** @@ -177,7 +179,7 @@ export interface IDataSource { export interface IDataSourceConfig { id: string; // 数据请求ID标识 isInit: boolean; // 是否为初始数据 支持表达式 值为true时,将在组件初始化渲染时自动发送当前数据请求 - type: 'fetch' | 'mtop' | 'jsonp' | 'custom' | 'doServer'; // 数据请求类型 + type: string; // 数据请求类型 'fetch' | 'mtop' | 'jsonp' | 'custom' requestHandler?: IJSExpression; // 自定义扩展的外部请求处理器 仅type='custom'时生效 options?: IFetchOptions; // 请求参数配置 每种请求类型对应不同参数 dataHandler?: IJSExpression; // 数据结果处理函数,形如:(data, err) => Object @@ -190,7 +192,7 @@ export interface IDataSourceConfig { * @interface IFetchOptions */ export interface IFetchOptions { - uri: string; // 请求地址 支持表达式 + url: string; // 请求地址 支持表达式 params?: { // 请求参数 [key: string]: any; @@ -202,6 +204,7 @@ export interface IFetchOptions { // 自定义请求头 [key: string]: string; }; + [extConfigName: string]: any; } export interface IBasicMeta { @@ -245,4 +248,5 @@ export interface IAppMeta { description?: string; // 应用描述 spma?: string; // 应用spma A位信息 creator?: string; // author + [otherAttrName: string]: any; } diff --git a/packages/code-generator/src/utils/children.ts b/packages/code-generator/src/utils/children.ts deleted file mode 100644 index 2f2d34fda..000000000 --- a/packages/code-generator/src/utils/children.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - ChildNodeItem, - ChildNodeType, - IComponentNodeItem, - IJSExpression, -} from '../types'; - -// tslint:disable-next-line: no-empty -const noop = () => []; - -export function handleChildren( - children: ChildNodeType, - handlers: { - string?: (input: string) => T[]; - expression?: (input: IJSExpression) => T[]; - node?: (input: IComponentNodeItem) => T[]; - common?: (input: unknown) => T[]; - }, -): T[] { - if (Array.isArray(children)) { - const list: ChildNodeItem[] = children as ChildNodeItem[]; - return list - .map(child => handleChildren(child, handlers)) - .reduce((p, c) => p.concat(c), []); - } else if (typeof children === 'string') { - const handler = handlers.string || handlers.common || noop; - return handler(children as string); - } else if ((children as IJSExpression).type === 'JSExpression') { - const handler = handlers.expression || handlers.common || noop; - return handler(children as IJSExpression); - } else { - const handler = handlers.node || handlers.common || noop; - return handler(children as IComponentNodeItem); - } -} diff --git a/packages/code-generator/src/utils/compositeType.ts b/packages/code-generator/src/utils/compositeType.ts new file mode 100644 index 000000000..b62b2afe3 --- /dev/null +++ b/packages/code-generator/src/utils/compositeType.ts @@ -0,0 +1,88 @@ +import { CompositeArray, CompositeValue, ICompositeObject } from '../types'; +import { generateExpression, isJsExpression } from './jsExpression'; + +type CustomHandler = (data: unknown) => string; +interface CustomHandlerSet { + boolean?: CustomHandler; + number?: CustomHandler; + string?: CustomHandler; + array?: CustomHandler; + object?: CustomHandler; + expression?: CustomHandler; +} + +function generateArray( + value: CompositeArray, + handlers: CustomHandlerSet = {}, +): string { + const body = value.map(v => generateUnknownType(v, handlers)).join(','); + return `[${body}]`; +} + +function generateObject( + value: ICompositeObject, + handlers: CustomHandlerSet = {}, +): string { + if (isJsExpression(value)) { + if (handlers.expression) { + return handlers.expression(value); + } + return generateExpression(value); + } + + const body = Object.keys(value) + .map(key => { + const v = generateUnknownType(value[key], handlers); + return `${key}: ${v}`; + }) + .join(',\n'); + + return `{${body}}`; +} + +export function generateUnknownType( + value: CompositeValue, + handlers: CustomHandlerSet = {}, +): string { + if (Array.isArray(value)) { + if (handlers.array) { + return handlers.array(value); + } + return generateArray(value as CompositeArray, handlers); + } else if (typeof value === 'object') { + if (handlers.object) { + return handlers.object(value); + } + return generateObject(value as ICompositeObject, handlers); + } else if (typeof value === 'string') { + if (handlers.string) { + return handlers.string(value); + } + return `'${value}'`; + } else if (typeof value === 'number' && handlers.number) { + return handlers.number(value); + } else if (typeof value === 'boolean' && handlers.boolean) { + return handlers.boolean(value); + } + return `${value}`; +} + +export function generateCompositeType( + value: CompositeValue, + handlers: CustomHandlerSet = {}, +): [boolean, string] { + const result = generateUnknownType(value, handlers); + + if (result.substr(0, 1) === "'" && result.substr(-1, 1) === "'") { + return [true, result.substring(1, result.length - 1)]; + } + + return [false, result]; +} + +export function handleStringValueDefault([isString, result]: [boolean, string]) { + if (isString) { + return `'${result}'`; + } + return result; +} diff --git a/packages/code-generator/src/utils/jsExpression.ts b/packages/code-generator/src/utils/jsExpression.ts new file mode 100644 index 000000000..9c8541692 --- /dev/null +++ b/packages/code-generator/src/utils/jsExpression.ts @@ -0,0 +1,85 @@ +import traverse from '@babel/traverse'; +import * as parser from '@babel/parser'; +import { CodeGeneratorError, IJSExpression } from '../types'; + +let count = 0; + +function test(functionBody: string) { + console.log(functionBody); + console.log('---->'); + try { + const parseResult = parser.parse(functionBody); + // console.log(JSON.stringify(parseResult)); + traverse(parseResult, { + enter(path) { + console.log('path: ', JSON.stringify(path)); + } + }); + + if (count === 0) { + count++; + + test('this.aaa && this.bbb'); + } + } catch (error) { + // console.log('Error'); + console.log(error.message); + } + console.log('====================='); +} + +export function transformFuncExpr2MethodMember( + methodName: string, + functionBody: string, +): string { + // test(functionBody); + + const args = getFuncExprArguments(functionBody); + const body = getFuncExprBody(functionBody); + + return `${methodName}(${args}) { ${body} }`; +} + +export function getFuncExprArguments(functionBody: string) { + const start = functionBody.indexOf('('); + const end = functionBody.indexOf(')'); + + if (start < 0 || end < 0 || end < start) { + throw new CodeGeneratorError('JSExpression has no valid arguments.'); + } + + const args = functionBody.slice(start + 1, end); + return args; +} + +export function getFuncExprBody(functionBody: string) { + const start = functionBody.indexOf('{'); + const end = functionBody.lastIndexOf('}'); + + // test(functionBody); + + if (start < 0 || end < 0 || end < start) { + throw new CodeGeneratorError('JSExpression has no valid body.'); + } + + const body = functionBody.slice(start + 1, end); + return body; +} + +export function generateExpression(value: any): string { + if (value && (value as IJSExpression).type === 'JSExpression') { + // test((value as IJSExpression).value); + + return (value as IJSExpression).value || 'null'; + } + + throw new CodeGeneratorError('Not a JSExpression'); +} + +export function isJsExpression(value: any): boolean { + return ( + value && + typeof value === 'object' && + (value as IJSExpression).type === 'JSExpression' + ); +} diff --git a/packages/code-generator/src/utils/nodeToJSX.ts b/packages/code-generator/src/utils/nodeToJSX.ts new file mode 100644 index 000000000..82673f851 --- /dev/null +++ b/packages/code-generator/src/utils/nodeToJSX.ts @@ -0,0 +1,221 @@ +import { + ChildNodeType, + IComponentNodeItem, + IInlineStyle, + IJSExpression, + ChildNodeItem, + CodeGeneratorError, + PIECE_TYPE, + CodePiece, + HandlerSet, + ExtGeneratorPlugin, +} from '../types'; +import { generateCompositeType } from './compositeType'; +import { generateExpression } from './jsExpression'; + +// tslint:disable-next-line: no-empty +const noop = () => []; + +export function handleChildren( + children: ChildNodeType, + handlers: HandlerSet, +): T[] { + if (Array.isArray(children)) { + const list: ChildNodeItem[] = children as ChildNodeItem[]; + return list + .map(child => handleChildren(child, handlers)) + .reduce((p, c) => p.concat(c), []); + } else if (typeof children === 'string') { + const handler = handlers.string || handlers.common || noop; + return handler(children as string); + } else if ((children as IJSExpression).type === 'JSExpression') { + const handler = handlers.expression || handlers.common || noop; + return handler(children as IJSExpression); + } else { + const handler = handlers.node || handlers.common || noop; + return handler(children as IComponentNodeItem); + } +} + +export function generateInlineStyle(style: IInlineStyle): string | null { + const attrLines = Object.keys(style).map((cssAttribute: string) => { + const [isString, valueStr] = generateCompositeType(style[cssAttribute]); + const valuePart = isString ? `'${valueStr}'` : valueStr; + return `${cssAttribute}: ${valuePart},`; + }); + + if (attrLines.length === 0) { + return null; + } + + return `{ ${attrLines.join('')} }`; +} + +export function generateAttr(attrName: string, attrValue: any): CodePiece[] { + if (attrName === 'initValue' || attrName === 'labelCol') { + return []; + } + const [isString, valueStr] = generateCompositeType(attrValue); + return [{ + value: `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`, + type: PIECE_TYPE.ATTR, + }]; +} + +export function generateAttrs(nodeItem: IComponentNodeItem): CodePiece[] { + const { className, style, ...props } = nodeItem.props; + let pieces: CodePiece[] = []; + if (className) { + pieces.push({ + value: `className="${className}"`, + type: PIECE_TYPE.ATTR, + }); + } + if (style) { + const inlineStyle = generateInlineStyle(style); + if (inlineStyle !== null) { + pieces.push({ + value: `style={${inlineStyle}}`, + type: PIECE_TYPE.ATTR, + }); + } + } + + Object.keys(props).forEach((propName: string) => + pieces = pieces.concat(generateAttr(propName, props[propName])), + ); + + return pieces; +} + +export function mapNodeName(src: string): string { + if (src === 'Div') { + return 'div'; + } + return src; +} + +export function generateBasicNode(nodeItem: IComponentNodeItem): CodePiece[] { + const pieces: CodePiece[] = []; + pieces.push({ + value: mapNodeName(nodeItem.componentName), + type: PIECE_TYPE.TAG, + }); + + return pieces; +} + +export function generateReactCtrlLine(nodeItem: IComponentNodeItem): CodePiece[] { + const pieces: CodePiece[] = []; + + if (nodeItem.loop && nodeItem.loopArgs) { + let loopDataExp; + if ((nodeItem.loop as IJSExpression).type === 'JSExpression') { + loopDataExp = `(${(nodeItem.loop as IJSExpression).value})`; + } else { + loopDataExp = JSON.stringify(nodeItem.loop); + } + pieces.unshift({ + value: `${loopDataExp}.map((${nodeItem.loopArgs[0]}, ${nodeItem.loopArgs[1]}) => (`, + type: PIECE_TYPE.BEFORE, + }); + pieces.push({ + value: '))', + type: PIECE_TYPE.AFTER, + }); + } + + if (nodeItem.condition) { + pieces.unshift({ + value: `(${generateCompositeType(nodeItem.condition)}) && (`, + type: PIECE_TYPE.BEFORE, + }); + pieces.push({ + value: ')', + type: PIECE_TYPE.AFTER, + }); + } + + if (nodeItem.condition || (nodeItem.loop && nodeItem.loopArgs)) { + pieces.unshift({ + value: '{', + type: PIECE_TYPE.BEFORE, + }); + pieces.push({ + value: '}', + type: PIECE_TYPE.AFTER, + }); + } + + return pieces; +} + +export function linkPieces(pieces: CodePiece[]): string { + if (pieces.filter(p => p.type === PIECE_TYPE.TAG).length !== 1) { + throw new CodeGeneratorError('One node only need one tag define'); + } + const tagName = pieces.filter(p => p.type === PIECE_TYPE.TAG)[0].value; + + const beforeParts = pieces + .filter(p => p.type === PIECE_TYPE.BEFORE) + .map(p => p.value) + .join(''); + + const afterParts = pieces + .filter(p => p.type === PIECE_TYPE.AFTER) + .map(p => p.value) + .join(''); + + const childrenParts = pieces + .filter(p => p.type === PIECE_TYPE.CHILDREN) + .map(p => p.value) + .join(''); + + let attrsParts = pieces + .filter(p => p.type === PIECE_TYPE.ATTR) + .map(p => p.value) + .join(' '); + + attrsParts = !!attrsParts ? ` ${attrsParts}` : ''; + + if (childrenParts) { + return `${beforeParts}<${tagName}${attrsParts}>${childrenParts}${afterParts}`; + } + + return `${beforeParts}<${tagName}${attrsParts} />${afterParts}`; +} + +export function createNodeGenerator(handlers: HandlerSet, plugins: ExtGeneratorPlugin[]) { + const generateNode = (nodeItem: IComponentNodeItem): string => { + let pieces: CodePiece[] = []; + + plugins.forEach(p => { + pieces = pieces.concat(p(nodeItem)); + }); + pieces = pieces.concat(generateBasicNode(nodeItem)); + pieces = pieces.concat(generateAttrs(nodeItem)); + if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) { + pieces = pieces.concat(handleChildren(nodeItem.children, handlers).map(l => ({ + type: PIECE_TYPE.CHILDREN, + value: l, + }))); + } + + return linkPieces(pieces); + }; + + handlers.node = (input: IComponentNodeItem) => [generateNode(input)]; + + return generateNode; +} + +export const generateString = (input: string) => [input]; + +export function createReactNodeGenerator() { + return createNodeGenerator({ + string: generateString, + expression: (input) => [generateExpression(input)], + }, [ + generateReactCtrlLine, + ]); +} diff --git a/packages/code-generator/src/utils/templateHelper.ts b/packages/code-generator/src/utils/templateHelper.ts new file mode 100644 index 000000000..f1692ad67 --- /dev/null +++ b/packages/code-generator/src/utils/templateHelper.ts @@ -0,0 +1,25 @@ +import { IResultFile, IResultDir } from '../types'; +import ResultDir from '../model/ResultDir'; + +type FuncFileGenerator = () => [string[], IResultFile]; + +export function insertFile(root: IResultDir, path: string[], file: IResultFile) { + let current: IResultDir = root; + path.forEach(pathNode => { + const dir = current.dirs.find(d => d.name === pathNode); + if (dir) { + current = dir; + } else { + const newDir = new ResultDir(pathNode); + current.addDirectory(newDir); + current = newDir; + } + }); + + current.addFile(file); +} + +export function runFileGenerator(root: IResultDir, fun: FuncFileGenerator) { + const [path, file] = fun(); + insertFile(root, path, file); +}