diff --git a/packages/code-generator/demo/demo.js b/packages/code-generator/demo/demo.js new file mode 100644 index 000000000..91e145de9 --- /dev/null +++ b/packages/code-generator/demo/demo.js @@ -0,0 +1,53 @@ +const fs = require('fs'); +const CodeGenerator = require('../lib').default; + +function flatFiles(rootName, dir) { + const dirRoot = rootName ? `${rootName}/${dir.name}` : dir.name; + const files = dir.files.map(file => ({ + name: `${dirRoot}/${file.name}.${file.ext}`, + content: file.content, + ext: '', + })); + const filesInSub = dir.dirs.map(subDir => flatFiles(`${dirRoot}`, subDir)); + const result = files.concat.apply(files, filesInSub); + + return result; +} + +function displayResultInConsole(root, fileName) { + const files = flatFiles('.', root); + files.forEach(file => { + if (!fileName || fileName === file.name) { + console.log(`========== ${file.name} Start ==========`); + console.log(file.content); + console.log(`========== ${file.name} End ==========`); + } + }); +} + +async function writeResultToDisk(root, path) { + const publisher = CodeGenerator.publishers.disk(); + + return publisher.publish({ + project: root, + outputPath: path, + projectSlug: 'demo-project', + createProjectFolder: true, + }); +} + +function main() { + const schemaJson = fs.readFileSync('./demo/sampleSchema.json', { encoding: 'utf8' }); + const createIceJsProjectBuilder = CodeGenerator.solutions.icejs; + const builder = createIceJsProjectBuilder(); + + builder.generateProject(schemaJson).then(result => { + displayResultInConsole(result); + writeResultToDisk(result, 'output/lowcodeDemo').then(response => + console.log('Write to disk: ', JSON.stringify(response)), + ); + return result; + }); +} + +main(); diff --git a/packages/code-generator/demo/sampleSchema.json b/packages/code-generator/demo/sampleSchema.json new file mode 100644 index 000000000..079289fac --- /dev/null +++ b/packages/code-generator/demo/sampleSchema.json @@ -0,0 +1,243 @@ +{ + "version": "1.0.0", + "componentsMap": [ + { + "componentName": "Button", + "package": "@alifd/next", + "version": "1.19.18", + "destructuring": true, + "exportName": "Button" + }, + { + "componentName": "Button.Group", + "package": "@alifd/next", + "version": "1.19.18", + "destructuring": true, + "exportName": "Button", + "subName": "Group" + }, + { + "componentName": "Input", + "package": "@alifd/next", + "version": "1.19.18", + "destructuring": true, + "exportName": "Input" + }, + { + "componentName": "Form", + "package": "@alifd/next", + "version": "1.19.18", + "destructuring": true, + "exportName": "Form" + }, + { + "componentName": "Form.Item", + "package": "@alifd/next", + "version": "1.19.18", + "destructuring": true, + "exportName": "Form", + "subName": "Item" + }, + { + "componentName": "NumberPicker", + "package": "@alifd/next", + "version": "1.19.18", + "destructuring": true, + "exportName": "NumberPicker" + }, + { + "componentName": "Select", + "package": "@alifd/next", + "version": "1.19.18", + "destructuring": true, + "exportName": "Select" + } + ], + "componentsTree": [ + { + "componentName": "Page", + "id": "node$1", + "meta": { + "title": "测试", + "router": "/" + }, + "props": { + "ref": "outterView", + "autoLoading": true + }, + "fileName": "test", + "state": { + "text": "outter" + }, + "lifeCycles": { + "componentDidMount": { + "type": "JSExpression", + "value": "function() { this.utils.request(this.props.url); }" + } + }, + "children": [ + { + "componentName": "Form", + "id": "node$2", + "props": { + "labelCol": { + "type": "JSExpression", + "value": "this.state.colNum" + }, + "style": {}, + "ref": "testForm" + }, + "children": [ + { + "componentName": "Form.Item", + "id": "node$3", + "props": { + "label": "姓名:", + "name": "name", + "initValue": "李雷" + }, + "children": [ + { + "componentName": "Input", + "id": "node$4", + "props": { + "placeholder": "请输入", + "size": "medium", + "style": { + "width": 320 + } + } + } + ] + }, + { + "componentName": "Form.Item", + "id": "node$5", + "props": { + "label": "年龄:", + "name": "age", + "initValue": "22" + }, + "children": [ + { + "componentName": "NumberPicker", + "id": "node$6", + "props": { + "size": "medium", + "type": "normal" + } + } + ] + }, + { + "componentName": "Form.Item", + "id": "node$7", + "props": { + "label": "职业:", + "name": "profession" + }, + "children": [ + { + "componentName": "Select", + "id": "node$8", + "props": { + "dataSource": [ + { + "label": "教师", + "value": "t" + }, + { + "label": "医生", + "value": "d" + }, + { + "label": "歌手", + "value": "s" + } + ] + } + } + ] + }, + { + "componentName": "Div", + "id": "node$9", + "props": { + "style": { + "textAlign": "center" + } + }, + "children": [ + { + "componentName": "Button.Group", + "id": "node$a", + "props": {}, + "children": [ + { + "componentName": "Button", + "id": "node$b", + "props": { + "type": "primary", + "style": { + "margin": "0 5px 0 5px" + }, + "htmlType": "submit" + }, + "children": [ + "提交" + ] + }, + { + "componentName": "Button", + "id": "node$d", + "props": { + "type": "normal", + "style": { + "margin": "0 5px 0 5px" + }, + "htmlType": "reset" + }, + "children": [ + "重置" + ] + } + ] + } + ] + } + ] + } + ] + } + ], + "constants": { + "ENV": "prod", + "DOMAIN": "xxx.alibaba-inc.com" + }, + "css": "body {font-size: 12px;} .table { width: 100px;}", + "config": { + "sdkVersion": "1.0.3", + "historyMode": "hash", + "targetRootID": "J_Container", + "layout": { + "componentName": "BasicLayout", + "props": { + "logo": "...", + "name": "测试网站" + } + }, + "theme": { + "package": "@alife/theme-fusion", + "version": "^0.1.0", + "primary": "#ff9966" + } + }, + "meta": { + "name": "demo应用", + "git_group": "appGroup", + "project_name": "app_demo", + "description": "这是一个测试应用", + "spma": "spa23d", + "creator": "月飞" + } +} diff --git a/packages/code-generator/package.json b/packages/code-generator/package.json index af7a4e06c..3bffb2689 100644 --- a/packages/code-generator/package.json +++ b/packages/code-generator/package.json @@ -8,17 +8,24 @@ ], "scripts": { "build": "rimraf lib && tsc", - "demo": "ts-node -r tsconfig-paths/register ./src/demo/main.ts", - "test": "ava" + "demo": "node ./demo/demo.js", + "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/main.ts b/packages/code-generator/src/demo/main.ts deleted file mode 100644 index e0d341ef4..000000000 --- a/packages/code-generator/src/demo/main.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { IResultDir, IResultFile } from '../types'; - -import CodeGenerator from '../index'; -import { createDiskPublisher } from '../publisher/disk'; -import demoSchema from './simpleDemo'; - -function flatFiles(rootName: string | null, dir: IResultDir): IResultFile[] { - const dirRoot: string = rootName ? `${rootName}/${dir.name}` : dir.name; - const files: IResultFile[] = dir.files.map(file => ({ - name: `${dirRoot}/${file.name}.${file.ext}`, - content: file.content, - ext: '', - })); - const filesInSub = dir.dirs.map(subDir => flatFiles(`${dirRoot}`, subDir)); - const result: IResultFile[] = files.concat.apply(files, filesInSub); - - return result; -} - -function displayResultInConsole(root: IResultDir, fileName?: string): void { - const files = flatFiles('.', root); - files.forEach(file => { - if (!fileName || fileName === file.name) { - console.log(`========== ${file.name} Start ==========`); - console.log(file.content); - console.log(`========== ${file.name} End ==========`); - } - }); -} - -async function writeResultToDisk(root: IResultDir, path: string): Promise { - const publisher = createDiskPublisher(); - - return publisher.publish({ - project: root, - outputPath: path, - projectSlug: 'demo-project', - createProjectFolder: true, - }); -} - -function main() { - const createIceJsProjectBuilder = CodeGenerator.solutions.icejs; - const builder = createIceJsProjectBuilder(); - builder.generateProject(demoSchema).then(result => { - // displayResultInConsole(result, '././src/routes.js'); - writeResultToDisk(result, '/Users/armslave/lowcodeDemo').then(response => - console.log('Write to disk: ', JSON.stringify(response)), - ); - }); -} - -main(); diff --git a/packages/code-generator/src/demo/simpleDemo.ts b/packages/code-generator/src/demo/simpleDemo.ts deleted file mode 100644 index 2e1f28a76..000000000 --- a/packages/code-generator/src/demo/simpleDemo.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { IProjectSchema } from '../types'; - -const demoData: IProjectSchema = { - version: '1.0.0', - componentsMap: [ - { - componentName: 'Button', - package: '@alifd/next', - version: '1.19.18', - destructuring: true, - exportName: 'Button', - }, - { - componentName: 'Button.Group', - package: '@alifd/next', - version: '1.19.18', - destructuring: true, - exportName: 'Button', - subName: 'Group', - }, - { - componentName: 'Input', - package: '@alifd/next', - version: '1.19.18', - destructuring: true, - exportName: 'Input', - }, - { - componentName: 'Form', - package: '@alifd/next', - version: '1.19.18', - destructuring: true, - exportName: 'Form', - }, - { - componentName: 'Form.Item', - package: '@alifd/next', - version: '1.19.18', - destructuring: true, - exportName: 'Form', - subName: 'Item', - }, - { - componentName: 'NumberPicker', - package: '@alifd/next', - version: '1.19.18', - destructuring: true, - exportName: 'NumberPicker', - }, - { - componentName: 'Select', - package: '@alifd/next', - version: '1.19.18', - destructuring: true, - exportName: 'Select', - }, - ], - componentsTree: [ - { - componentName: 'Page', - id: 'node_1', - meta: { - title: '测试', - router: '/', - }, - props: { - ref: 'outterView', - autoLoading: true, - }, - fileName: 'test', - state: { - text: 'outter', - }, - children: [ - { - componentName: 'Form', - id: 'node_2', - props: { - labelCol: 4, - style: {}, - ref: 'testForm', - }, - children: [ - { - componentName: 'Form.Item', - id: 'node_3', - props: { - label: '姓名:', - name: 'name', - initValue: '李雷', - }, - children: [ - { - componentName: 'Input', - id: 'node_4', - props: { - placeholder: '请输入', - size: 'medium', - style: { - width: 320, - }, - }, - }, - ], - }, - { - componentName: 'Form.Item', - id: 'node_5', - props: { - label: '年龄:', - name: 'age', - initValue: '22', - }, - children: [ - { - componentName: 'NumberPicker', - id: 'node_6', - props: { - size: 'medium', - type: 'normal', - }, - }, - ], - }, - { - componentName: 'Form.Item', - id: 'node_7', - props: { - label: '职业:', - name: 'profession', - }, - children: [ - { - componentName: 'Select', - id: 'node_8', - props: { - dataSource: [ - { - label: '教师', - value: 't', - }, - { - label: '医生', - value: 'd', - }, - { - label: '歌手', - value: 's', - }, - ], - }, - }, - ], - }, - { - componentName: 'Div', - id: 'node_9', - props: { - style: { - textAlign: 'center', - }, - }, - children: [ - { - componentName: 'Button.Group', - id: 'node_a', - props: {}, - children: [ - { - componentName: 'Button', - id: 'node_b', - props: { - type: 'primary', - style: { - margin: '0 5px 0 5px', - }, - htmlType: 'submit', - }, - children: ['提交'], - }, - { - componentName: 'Button', - id: 'node_d', - props: { - type: 'normal', - style: { - margin: '0 5px 0 5px', - }, - htmlType: 'reset', - }, - children: ['重置'], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - constants: { - ENV: 'prod', - DOMAIN: 'xxx.alibaba-inc.com', - }, - css: 'body {font-size: 12px;} .table { width: 100px;}', - config: { - sdkVersion: '1.0.3', - historyMode: 'hash', - targetRootID: 'J_Container', - layout: { - componentName: 'BasicLayout', - props: { - logo: '...', - name: '测试网站', - }, - }, - theme: { - package: '@alife/theme-fusion', - version: '^0.1.0', - primary: '#ff9966', - }, - }, - meta: { - name: 'demo应用', - git_group: 'appGroup', - project_name: 'app_demo', - description: '这是一个测试应用', - spma: 'spa23d', - creator: '月飞', - }, -}; - -export default demoData; diff --git a/packages/code-generator/src/generator/CodeBuilder.ts b/packages/code-generator/src/generator/CodeBuilder.ts index 1e5c3c999..ad7f33622 100644 --- a/packages/code-generator/src/generator/CodeBuilder.ts +++ b/packages/code-generator/src/generator/CodeBuilder.ts @@ -62,10 +62,12 @@ export default class Builder implements ICodeBuilder { } unprocessedChunks.splice(indexToRemove, 1); - unprocessedChunks.forEach( - // remove the processed chunk from all the linkAfter arrays from the remaining chunks - ch => (ch.linkAfter = ch.linkAfter.filter(after => after !== 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)), + ); + } } return resultingString.join('\n'); diff --git a/packages/code-generator/src/generator/ModuleBuilder.ts b/packages/code-generator/src/generator/ModuleBuilder.ts index 8115f0def..991ffedec 100644 --- a/packages/code-generator/src/generator/ModuleBuilder.ts +++ b/packages/code-generator/src/generator/ModuleBuilder.ts @@ -1,18 +1,24 @@ import { BuilderComponentPlugin, CodeGeneratorError, + IBasicSchema, ICodeChunk, ICompiledModule, IModuleBuilder, + IParseResult, + IResultDir, IResultFile, + ISchemaParser, PostProcessor, } from '../types'; 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'; export function createModuleBuilder( @@ -66,6 +72,20 @@ export function createModuleBuilder( }; }; + const generateModuleCode = async (schema: IBasicSchema | string): Promise => { + // Init + const schemaParser: ISchemaParser = new SchemaParser(); + const parseResult: IParseResult = schemaParser.parse(schema); + + const containerInfo = parseResult.containers[0]; + const { files } = await generateModule(containerInfo); + + const dir = new ResultDir(containerInfo.moduleName); + files.forEach(file => dir.addFile(file)); + + return dir; + } + const linkCodeChunks = ( chunks: Record, fileName: string, @@ -88,6 +108,7 @@ export function createModuleBuilder( return { generateModule, + generateModuleCode, linkCodeChunks, addPlugin: chunkGenerator.addPlugin.bind(chunkGenerator), }; diff --git a/packages/code-generator/src/generator/ProjectBuilder.ts b/packages/code-generator/src/generator/ProjectBuilder.ts index ada2e47fd..ec1c897b6 100644 --- a/packages/code-generator/src/generator/ProjectBuilder.ts +++ b/packages/code-generator/src/generator/ProjectBuilder.ts @@ -57,8 +57,8 @@ export class ProjectBuilder implements IProjectBuilder { this.postProcessors = postProcessors; } - public async generateProject(schema: IProjectSchema): Promise { - // Init working parts + public async generateProject(schema: IProjectSchema | string): Promise { + // Init const schemaParser: ISchemaParser = new SchemaParser(); const builders = this.createModuleBuilders(); const projectRoot = this.template.generateTemplate(); @@ -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..9de9c1538 100644 --- a/packages/code-generator/src/index.ts +++ b/packages/code-generator/src/index.ts @@ -3,17 +3,78 @@ * */ import { createProjectBuilder } from './generator/ProjectBuilder'; +import { createModuleBuilder } from './generator/ModuleBuilder'; import { createDiskPublisher } from './publisher/disk'; import createIceJsProjectBuilder from './solutions/icejs'; +import createRecoreProjectBuilder from './solutions/recore'; + +// 引入说明 +import { REACT_CHUNK_NAME } from './plugins/component/react/const'; + +// 引入通用插件组 +import esmodule from './plugins/common/esmodule'; +import requireUtils from './plugins/common/requireUtils'; +import containerClass from './plugins/component/react/containerClass'; +import containerDataSource from './plugins/component/react/containerDataSource'; +import containerInitState from './plugins/component/react/containerInitState'; +import containerInjectUtils from './plugins/component/react/containerInjectUtils'; +import containerLifeCycle from './plugins/component/react/containerLifeCycle'; +import containerMethod from './plugins/component/react/containerMethod'; +import jsx from './plugins/component/react/jsx'; +import reactCommonDeps from './plugins/component/react/reactCommonDeps'; +import css from './plugins/component/style/css'; +import constants from './plugins/project/constants'; +import i18n from './plugins/project/i18n'; +import utils from './plugins/project/utils'; + +// 引入常用工具 +import * as utilsCommon from './utils/common'; +import * as utilsCompositeType from './utils/compositeType'; +import * as utilsJsExpression from './utils/jsExpression'; +import * as utilsNodeToJSX from './utils/nodeToJSX'; +import * as utilsTemplateHelper from './utils/templateHelper'; export * from './types'; export default { createProjectBuilder, + createModuleBuilder, solutions: { icejs: createIceJsProjectBuilder, + recore: createRecoreProjectBuilder, }, publishers: { disk: createDiskPublisher, }, + plugins: { + common: { + esmodule, + requireUtils, + }, + react: { + containerClass, + containerDataSource, + containerInitState, + containerInjectUtils, + containerLifeCycle, + containerMethod, + jsx, + reactCommonDeps, + }, + style: { + css, + }, + project: { + constants, + i18n, + utils, + }, + }, + utils: { + common: utilsCommon, + compositeType: utilsCompositeType, + jsExpression: utilsJsExpression, + nodeToJSX: utilsNodeToJSX, + templateHelper: utilsTemplateHelper, + }, }; diff --git a/packages/code-generator/src/parser/SchemaParser.ts b/packages/code-generator/src/parser/SchemaParser.ts index 4288d894b..d6b679ff0 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: {}, @@ -45,12 +46,23 @@ class SchemaParser implements ISchemaParser { return true; } - public parse(schema: IProjectSchema): IParseResult { + public parse(schemaSrc: IProjectSchema | string): IParseResult { // TODO: collect utils depends in JSExpression const compDeps: Record = {}; const internalDeps: Record = {}; let utilsDeps: IExternalDependency[] = []; + let schema: IProjectSchema; + if (typeof schemaSrc === 'string') { + try { + schema = JSON.parse(schemaSrc); + } catch (error) { + throw new CodeGeneratorError(`Parse schema failed: ${error.message || 'unknown reason'}`); + } + } else { + schema = schemaSrc; + } + // 解析三方组件依赖 schema.componentsMap.forEach(info => { info.dependencyType = DependencyType.External; @@ -78,7 +90,7 @@ class SchemaParser implements ISchemaParser { const container: IContainerInfo = { ...subRoot, containerType: subRoot.componentName, - componentName: subRoot.fileName, + moduleName: subRoot.fileName, // TODO: 驼峰化名称 }; return container; }); @@ -104,9 +116,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 +143,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/common/esmodule.ts b/packages/code-generator/src/plugins/common/esmodule.ts index 7043a4aa9..316ed5821 100644 --- a/packages/code-generator/src/plugins/common/esmodule.ts +++ b/packages/code-generator/src/plugins/common/esmodule.ts @@ -2,6 +2,7 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, CodeGeneratorError, DependencyType, @@ -41,7 +42,7 @@ function groupDepsByPack(deps: IDependency[]): Record { function buildPackageImport( pkg: string, deps: IDependency[], - isJSX: boolean, + targetFileType: string, ): ICodeChunk[] { const chunks: ICodeChunk[] = []; let defaultImport: string = ''; @@ -58,7 +59,7 @@ function buildPackageImport( if (dep.subName) { chunks.push({ type: ChunkType.STRING, - fileType: isJSX ? FileType.JSX : FileType.JS, + fileType: targetFileType, name: COMMON_CHUNK_NAME.FileVarDefine, content: `const ${targetName} = ${srcName}.${dep.subName};`, linkAfter: [ @@ -103,7 +104,7 @@ function buildPackageImport( statementL.push(`'@/${(deps[0] as IInternalDependency).type}/${pkg}';`); chunks.push({ type: ChunkType.STRING, - fileType: isJSX ? FileType.JSX : FileType.JS, + fileType: targetFileType, name: COMMON_CHUNK_NAME.InternalDepsImport, content: statementL.join(' '), linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], @@ -112,7 +113,7 @@ function buildPackageImport( statementL.push(`'${pkg}';`); chunks.push({ type: ChunkType.STRING, - fileType: isJSX ? FileType.JSX : FileType.JS, + fileType: targetFileType, name: COMMON_CHUNK_NAME.ExternalDepsImport, content: statementL.join(' '), linkAfter: [], @@ -122,25 +123,36 @@ function buildPackageImport( return chunks; } -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +type PluginConfig = { + fileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?: PluginConfig) => { + const cfg: PluginConfig = { + fileType: FileType.JS, + ...config, }; - const isJSX = next.chunks.some(chunk => chunk.fileType === FileType.JSX); + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; - const ir = next.ir as IWithDependency; + const ir = next.ir as IWithDependency; - if (ir && ir.deps && ir.deps.length > 0) { - const packs = groupDepsByPack(ir.deps); + if (ir && ir.deps && ir.deps.length > 0) { + const packs = groupDepsByPack(ir.deps); - Object.keys(packs).forEach(pkg => { - const chunks = buildPackageImport(pkg, packs[pkg], isJSX); - next.chunks.push.apply(next.chunks, chunks); - }); - } + Object.keys(packs).forEach(pkg => { + const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType); + next.chunks.push.apply(next.chunks, chunks); + }); + } - return next; + return next; + }; + + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/common/requireUtils.ts b/packages/code-generator/src/plugins/common/requireUtils.ts index d5b4747b9..9da850a4c 100644 --- a/packages/code-generator/src/plugins/common/requireUtils.ts +++ b/packages/code-generator/src/plugins/common/requireUtils.ts @@ -2,26 +2,30 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../types'; // TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +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.InternalDepsImport, + content: `import * from 'react';`, + linkAfter: [], + }); + + return next; }; - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: COMMON_CHUNK_NAME.InternalDepsImport, - content: `import * from 'react';`, - linkAfter: [], - }); - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; 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 ddf1ef337..7ca5e685f 100644 --- a/packages/code-generator/src/plugins/component/react/containerClass.ts +++ b/packages/code-generator/src/plugins/component/react/containerClass.ts @@ -1,101 +1,104 @@ -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 { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +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.JSX, + name: CLASS_DEFINE_CHUNK_NAME.Start, + content: `class ${ir.moduleName} extends React.Component {`, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + 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, REACT_CHUNK_NAME.ClassRenderEnd], + }); + + 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: REACT_CHUNK_NAME.ClassRenderStart, + content: 'render() {', + linkAfter: [ + CLASS_DEFINE_CHUNK_NAME.Start, + CLASS_DEFINE_CHUNK_NAME.ConstructorEnd, + CLASS_DEFINE_CHUNK_NAME.InsMethod, + ], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JSX, + name: REACT_CHUNK_NAME.ClassRenderEnd, + content: '}', + 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};`, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + CLASS_DEFINE_CHUNK_NAME.End, + ], + }); + + return next; }; - - const ir = next.ir as IContainerInfo; - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassStart, - content: `class ${ir.componentName} extends React.Component {`, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - ], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassEnd, - content: `}`, - linkAfter: [REACT_CHUNK_NAME.ClassStart, REACT_CHUNK_NAME.ClassRenderEnd], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorStart, - content: 'constructor(props, context) { super(props); ', - linkAfter: [REACT_CHUNK_NAME.ClassStart], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorEnd, - content: '}', - linkAfter: [ - REACT_CHUNK_NAME.ClassConstructorStart, - REACT_CHUNK_NAME.ClassConstructorContent, - ], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassRenderStart, - content: 'render() {', - linkAfter: [ - REACT_CHUNK_NAME.ClassStart, - REACT_CHUNK_NAME.ClassConstructorEnd, - REACT_CHUNK_NAME.ClassLifeCycle, - REACT_CHUNK_NAME.ClassMethod, - ], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassRenderEnd, - content: '}', - 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.componentName};`, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - REACT_CHUNK_NAME.ClassEnd, - ], - }); - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/react/containerDataSource.ts b/packages/code-generator/src/plugins/component/react/containerDataSource.ts index c71859a21..72d7efd6b 100644 --- a/packages/code-generator/src/plugins/component/react/containerDataSource.ts +++ b/packages/code-generator/src/plugins/component/react/containerDataSource.ts @@ -1,39 +1,52 @@ -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, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +type PluginConfig = { + fileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, }; - const ir = next.ir as IContainerInfo; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; - if (ir.state) { - const state = ir.state; - const fields = Object.keys(state).map(stateName => { - const [isString, value] = generateCompositeType(state[stateName]); - return `${stateName}: ${isString ? `'${value}'` : value},`; - }); + const ir = next.ir as IContainerInfo; - 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 (ir.state) { + const state = ir.state; + const fields = Object.keys(state).map(stateName => { + const [isString, value] = generateCompositeType(state[stateName]); + return `${stateName}: ${isString ? `'${value}'` : value},`; + }); - return next; + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, + content: `this.state = { ${fields.join('')} };`, + linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], + }); + } + + return next; + }; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/react/containerInitState.ts b/packages/code-generator/src/plugins/component/react/containerInitState.ts index c71859a21..50984c961 100644 --- a/packages/code-generator/src/plugins/component/react/containerInitState.ts +++ b/packages/code-generator/src/plugins/component/react/containerInitState.ts @@ -1,39 +1,64 @@ -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, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +type PluginConfig = { + fileType: string; + implementType: 'inConstructor' | 'insMember' | 'hooks'; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + implementType: 'inConstructor', + ...config, }; - const ir = next.ir as IContainerInfo; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; - if (ir.state) { - const state = ir.state; - const fields = Object.keys(state).map(stateName => { - const [isString, value] = generateCompositeType(state[stateName]); - return `${stateName}: ${isString ? `'${value}'` : value},`; - }); + const ir = next.ir as IContainerInfo; - 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 (ir.state) { + const state = ir.state; + const fields = Object.keys(state).map(stateName => { + const [isString, value] = generateCompositeType(state[stateName]); + return `${stateName}: ${isString ? `'${value}'` : value},`; + }); - return next; + 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; + }; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/react/containerInjectUtils.ts b/packages/code-generator/src/plugins/component/react/containerInjectUtils.ts index a6670230a..8cded167b 100644 --- a/packages/code-generator/src/plugins/component/react/containerInjectUtils.ts +++ b/packages/code-generator/src/plugins/component/react/containerInjectUtils.ts @@ -1,26 +1,39 @@ -import { REACT_CHUNK_NAME } from './const'; +import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, } from '../../../types'; -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +type PluginConfig = { + fileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, }; - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorContent, - content: `this.utils = utils;`, - linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], - }); + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; - return next; + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, + content: `this.utils = utils;`, + linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart], + }); + + return next; + }; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts b/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts index 3e83e659a..d1951d8e2 100644 --- a/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts +++ b/packages/code-generator/src/plugins/component/react/containerLifeCycle.ts @@ -1,12 +1,14 @@ +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, + BuilderComponentPluginFactory, ChunkType, CodeGeneratorError, FileType, @@ -16,66 +18,73 @@ import { IJSExpression, } from '../../../types'; -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +type PluginConfig = { + fileType: string; + exportNameMapping: Record; + normalizeNameMapping: Record; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + exportNameMapping: {}, + normalizeNameMapping: {}, + ...config, }; - const ir = next.ir as IContainerInfo; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IContainerInfo; + + if (ir.lifeCycles) { + const lifeCycles = ir.lifeCycles; + const chunks = Object.keys(lifeCycles).map(lifeCycleName => { + const normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName; + const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName; + if (normalizeName === 'constructor') { + return { + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent, + content: getFuncExprBody( + (lifeCycles[lifeCycleName] as IJSExpression).value, + ), + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]], + }; + } + if (normalizeName === 'render') { + return { + type: ChunkType.STRING, + fileType: cfg.fileType, + name: REACT_CHUNK_NAME.ClassRenderPre, + content: getFuncExprBody( + (lifeCycles[lifeCycleName] as IJSExpression).value, + ), + linkAfter: [REACT_CHUNK_NAME.ClassRenderStart], + }; + } - if (ir.lifeCycles) { - const lifeCycles = ir.lifeCycles; - const chunks = Object.keys(lifeCycles).map(lifeCycleName => { - if (lifeCycleName === 'constructor') { return { type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassConstructorContent, - content: getFuncExprBody( - (lifeCycles[lifeCycleName] as IJSExpression).value, - ), - linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], - }; - } - if (lifeCycleName === 'render') { - return { - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassRenderPre, - content: getFuncExprBody( - (lifeCycles[lifeCycleName] as IJSExpression).value, - ), - 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, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsMethod, content: transformFuncExpr2MethodMember( - lifeCycleName, + exportName, (lifeCycles[lifeCycleName] as IJSExpression).value, ), - linkAfter: [ - REACT_CHUNK_NAME.ClassStart, - REACT_CHUNK_NAME.ClassConstructorEnd, - ], + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], }; - } + }); - throw new CodeGeneratorError('Unknown life cycle method name'); - }); + next.chunks.push.apply(next.chunks, chunks); + } - next.chunks.push.apply(next.chunks, chunks); - } - - return next; + return next; + }; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/react/containerMethod.ts b/packages/code-generator/src/plugins/component/react/containerMethod.ts index cff01d9ce..9bba467da 100644 --- a/packages/code-generator/src/plugins/component/react/containerMethod.ts +++ b/packages/code-generator/src/plugins/component/react/containerMethod.ts @@ -1,9 +1,10 @@ -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, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeChunk, @@ -12,34 +13,42 @@ import { IJSExpression, } from '../../../types'; -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +type PluginConfig = { + fileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, }; - const ir = next.ir as IContainerInfo; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; - if (ir.methods) { - const methods = ir.methods; - const chunks = Object.keys(methods).map(methodName => ({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassMethod, - content: transformFuncExpr2MethodMember( - methodName, - (methods[methodName] as IJSExpression).value, - ), - linkAfter: [ - REACT_CHUNK_NAME.ClassStart, - REACT_CHUNK_NAME.ClassConstructorEnd, - REACT_CHUNK_NAME.ClassLifeCycle, - ], - })); + const ir = next.ir as IContainerInfo; - next.chunks.push.apply(next.chunks, chunks); - } + if (ir.methods) { + const methods = ir.methods; + const chunks = Object.keys(methods).map(methodName => ({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsMethod, + content: transformFuncExpr2MethodMember( + methodName, + (methods[methodName] as IJSExpression).value, + ), + linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]], + })); - return next; + next.chunks.push.apply(next.chunks, chunks); + } + + return next; + }; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/react/jsx.ts b/packages/code-generator/src/plugins/component/react/jsx.ts index 3e5162353..bb6b8f383 100644 --- a/packages/code-generator/src/plugins/component/react/jsx.ts +++ b/packages/code-generator/src/plugins/component/react/jsx.ts @@ -1,149 +1,50 @@ import { BuilderComponentPlugin, - ChildNodeItem, - ChildNodeType, + BuilderComponentPluginFactory, 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('')} }`; +type 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}}`}`; -} - -function mapNodeName(src: string): string { - if (src === 'Div') { - return 'div'; - } - return src; -} - -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 plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.JSX, + ...config, }; - const ir = next.ir as IContainerInfo; + const generator = createReactNodeGenerator(); - 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 plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: REACT_CHUNK_NAME.ClassRenderJSX, - content: `return ${jsxContent};`, - linkAfter: [ - REACT_CHUNK_NAME.ClassRenderStart, - REACT_CHUNK_NAME.ClassRenderPre, - ], - }); + const ir = next.ir as IContainerInfo; + const jsxContent = generator(ir); - return next; + 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, + ], + }); + + return next; + }; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts b/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts index 38936fc68..2e75a5b7e 100644 --- a/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts +++ b/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts @@ -2,27 +2,30 @@ import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +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: `import React from 'react';`, + linkAfter: [], + }); + + return next; }; - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: COMMON_CHUNK_NAME.ExternalDepsImport, - content: `import React from 'react';`, - linkAfter: [], - }); - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; 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..87d496aaf --- /dev/null +++ b/packages/code-generator/src/plugins/component/recore/pageDataSource.ts @@ -0,0 +1,67 @@ +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, + ...rest + } = dataSource; + + let attrs: string[] = []; + + const extConfigs = Object.keys(rest).map(extConfigName => { + const value = (rest as Record)[extConfigName]; + const [isString, valueStr] = generateCompositeType(value); + return `${extConfigName}: ${isString ? `'${valueStr}'` : valueStr}`; + }); + + attrs = [...attrs, ...extConfigs]; + + 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 c4ce2160e..a2e8b46f0 100644 --- a/packages/code-generator/src/plugins/component/style/css.ts +++ b/packages/code-generator/src/plugins/component/style/css.ts @@ -2,36 +2,51 @@ import { COMMON_CHUNK_NAME } from '../../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IContainerInfo, } from '../../../types'; -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +type PluginConfig = { + fileType: string; + moduleFileType: string; +} + +const pluginFactory: BuilderComponentPluginFactory = (config?) => { + const cfg: PluginConfig = { + fileType: FileType.CSS, + moduleFileType: FileType.JSX, + ...config, }; - const ir = next.ir as IContainerInfo; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.CSS, - name: COMMON_CHUNK_NAME.StyleCssContent, - content: ir.css, - linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], - }); + const ir = next.ir as IContainerInfo; - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JSX, - name: COMMON_CHUNK_NAME.InternalDepsImport, - content: `import './index.css';`, - linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], - }); + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: COMMON_CHUNK_NAME.StyleCssContent, + content: ir.css, + linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], + }); - return next; + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.moduleFileType, + name: COMMON_CHUNK_NAME.InternalDepsImport, + content: `import './index.${cfg.fileType}';`, + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], + }); + + return next; + }; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/project/constants.ts b/packages/code-generator/src/plugins/project/constants.ts index 7be166d3d..5c02e9711 100644 --- a/packages/code-generator/src/plugins/project/constants.ts +++ b/packages/code-generator/src/plugins/project/constants.ts @@ -1,54 +1,57 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; -import { generateCompositeType } from '../../plugins/utils/compositeType'; +import { generateCompositeType } from '../../utils/compositeType'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../types'; -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IProjectInfo; + if (ir.constants) { + const [, constantStr] = generateCompositeType(ir.constants); + + 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.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, + ], + }); + } + + return next; }; - - const ir = next.ir as IProjectInfo; - if (ir.constants) { - const [, constantStr] = generateCompositeType(ir.constants); - - 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.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, - ], - }); - } - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; 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 f32d9abed..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 @@ -2,54 +2,57 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +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.JS, + name: COMMON_CHUNK_NAME.ExternalDepsImport, + content: ` + import { createApp } from 'ice'; + `, + linkAfter: [], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileMainContent, + content: ` + const appConfig = { + app: { + rootId: '${ir.config.targetRootID}', + }, + router: { + type: '${ir.config.historyMode}', + }, + }; + createApp(appConfig); + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + ], + }); + + return next; }; - - const ir = next.ir as IProjectInfo; - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.ExternalDepsImport, - content: ` - import { createApp } from 'ice'; - `, - linkAfter: [], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileMainContent, - content: ` - const appConfig = { - app: { - rootId: '${ir.config.targetRootID}', - }, - router: { - type: '${ir.config.historyMode}', - }, - }; - createApp(appConfig); - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - ], - }); - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; 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 6ceebc04a..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 @@ -2,42 +2,45 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +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.HTML, + name: COMMON_CHUNK_NAME.HtmlContent, + content: ` + + + + + + + ${ir.meta.name} + + +
+ + + `, + linkAfter: [], + }); + + return next; }; - - const ir = next.ir as IProjectInfo; - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.HTML, - name: COMMON_CHUNK_NAME.HtmlContent, - content: ` - - - - - - - ${ir.meta.name} - - -
- - - `, - linkAfter: [], - }); - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; 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 3e083c1f1..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 @@ -2,52 +2,55 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../../../../types'; -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +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.SCSS, + name: COMMON_CHUNK_NAME.StyleDepsImport, + content: ` + // 引入默认全局样式 + @import '@alifd/next/reset.scss'; + `, + linkAfter: [], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.SCSS, + name: COMMON_CHUNK_NAME.StyleCssContent, + content: ` + body { + -webkit-font-smoothing: antialiased; + } + `, + linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.SCSS, + name: COMMON_CHUNK_NAME.StyleCssContent, + content: ir.css || '', + linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], + }); + + return next; }; - - const ir = next.ir as IProjectInfo; - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.SCSS, - name: COMMON_CHUNK_NAME.StyleDepsImport, - content: ` - // 引入默认全局样式 - @import '@alifd/next/reset.scss'; - `, - linkAfter: [], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.SCSS, - name: COMMON_CHUNK_NAME.StyleCssContent, - content: ` - body { - -webkit-font-smoothing: antialiased; - } - `, - linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.SCSS, - name: COMMON_CHUNK_NAME.StyleCssContent, - content: ir.css || '', - linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], - }); - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; 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 0e4c48805..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 @@ -2,6 +2,7 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, @@ -20,67 +21,69 @@ interface IIceJsPackageJSON extends IPackageJSON { originTemplate: string; } -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IProjectInfo; + + const packageJson: IIceJsPackageJSON = { + name: '@alifd/scaffold-lite-js', + version: '0.1.5', + description: '轻量级模板,使用 JavaScript,仅包含基础的 Layout。', + dependencies: { + moment: '^2.24.0', + react: '^16.4.1', + 'react-dom': '^16.4.1', + '@alifd/theme-design-pro': '^0.x', + }, + devDependencies: { + '@ice/spec': '^1.0.0', + 'build-plugin-fusion': '^0.1.0', + 'build-plugin-moment-locales': '^0.1.0', + eslint: '^6.0.1', + 'ice.js': '^1.0.0', + stylelint: '^13.2.0', + '@ali/build-plugin-ice-def': '^0.1.0', + }, + scripts: { + start: 'icejs start', + build: 'icejs build', + lint: 'npm run eslint && npm run stylelint', + eslint: 'eslint --cache --ext .js,.jsx ./', + stylelint: 'stylelint ./**/*.scss', + }, + ideMode: { + name: 'ice-react', + }, + iceworks: { + type: 'react', + adapter: 'adapter-react-v3', + }, + engines: { + node: '>=8.0.0', + }, + repository: { + type: 'git', + url: 'http://gitlab.alibaba-inc.com/msd/leak-scan/tree/master', + }, + private: true, + originTemplate: '@alifd/scaffold-lite-js', + }; + + next.chunks.push({ + type: ChunkType.JSON, + fileType: FileType.JSON, + name: COMMON_CHUNK_NAME.FileMainContent, + content: packageJson, + linkAfter: [], + }); + + return next; }; - - const ir = next.ir as IProjectInfo; - - const packageJson: IIceJsPackageJSON = { - name: '@alifd/scaffold-lite-js', - version: '0.1.5', - description: '轻量级模板,使用 JavaScript,仅包含基础的 Layout。', - dependencies: { - moment: '^2.24.0', - react: '^16.4.1', - 'react-dom': '^16.4.1', - '@alifd/theme-design-pro': '^0.x', - }, - devDependencies: { - '@ice/spec': '^1.0.0', - 'build-plugin-fusion': '^0.1.0', - 'build-plugin-moment-locales': '^0.1.0', - eslint: '^6.0.1', - 'ice.js': '^1.0.0', - stylelint: '^13.2.0', - '@ali/build-plugin-ice-def': '^0.1.0', - }, - scripts: { - start: 'icejs start', - build: 'icejs build', - lint: 'npm run eslint && npm run stylelint', - eslint: 'eslint --cache --ext .js,.jsx ./', - stylelint: 'stylelint ./**/*.scss', - }, - ideMode: { - name: 'ice-react', - }, - iceworks: { - type: 'react', - adapter: 'adapter-react-v3', - }, - engines: { - node: '>=8.0.0', - }, - repository: { - type: 'git', - url: 'http://gitlab.alibaba-inc.com/msd/leak-scan/tree/master', - }, - private: true, - originTemplate: '@alifd/scaffold-lite-js', - }; - - next.chunks.push({ - type: ChunkType.JSON, - fileType: FileType.JSON, - name: COMMON_CHUNK_NAME.FileMainContent, - content: packageJson, - linkAfter: [], - }); - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; 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 b2c9c0805..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 @@ -2,78 +2,81 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IRouterInfo, } from '../../../../../types'; -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IRouterInfo; + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.InternalDepsImport, + content: ` + import BasicLayout from '@/layouts/BasicLayout'; + `, + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileVarDefine, + content: ` + const routerConfig = [ + { + path: '/', + component: BasicLayout, + children: [ + ${ir.routes + .map( + route => ` + { + path: '${route.path}', + component: ${route.componentName}, + } + `, + ) + .join(',')} + ], + }, + ]; + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileUtilDefine, + ], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileExport, + content: ` + export default routerConfig; + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileUtilDefine, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileMainContent, + ], + }); + + return next; }; - - const ir = next.ir as IRouterInfo; - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.InternalDepsImport, - content: ` - import BasicLayout from '@/layouts/BasicLayout'; - `, - linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileVarDefine, - content: ` - const routerConfig = [ - { - path: '/', - component: BasicLayout, - children: [ - ${ir.routes - .map( - route => ` - { - path: '${route.path}', - component: ${route.componentName}, - } - `, - ) - .join(',')} - ], - }, - ]; - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileUtilDefine, - ], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileExport, - content: ` - export default routerConfig; - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileUtilDefine, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileMainContent, - ], - }); - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; 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/.editorconfig.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/.editorconfig.ts new file mode 100644 index 000000000..737b32b80 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/.editorconfig.ts @@ -0,0 +1,32 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + '.editorconfig', + '', + ` +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Tab indentation +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + + `, + ); + + return [[], file]; +} + \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/.eslintignore.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/.eslintignore.ts new file mode 100644 index 000000000..42c1be15c --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/.eslintignore.ts @@ -0,0 +1,26 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + '.eslintignore', + '', + ` +.idea +.vscode +.theia +.recore +build/ +.* +~* +node_modules + +packages/solution + + `, + ); + + return [[], file]; +} + \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/.gitignore.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/.gitignore.ts new file mode 100644 index 000000000..fd89e80eb --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/.gitignore.ts @@ -0,0 +1,57 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + '.gitignore', + '', + ` +node_modules/ +coverage/ +build/ +dist/ +.idea/ +.vscode/ +.theia/ +.recore/ +.Trash-*/ +~* +package-lock.json + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store + *.swp +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + + `, + ); + + return [[], file]; +} + \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/.prettierrc.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/.prettierrc.ts new file mode 100644 index 000000000..19cd24fbc --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/.prettierrc.ts @@ -0,0 +1,22 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + '.prettierrc', + '', + ` +{ + "semi": true, + "singleQuote": true, + "printWidth": 120, + "trailingComma": "all" +} + + `, + ); + + return [[], file]; +} + \ No newline at end of file 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 new file mode 100644 index 000000000..f90fd7d87 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/README.md.ts @@ -0,0 +1,35 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'README', + 'md', + ` +# runtime-code + +乐高接出码模块测试项目 + + +## 安装运行 + +\`\`\`bash +# install dependencies +tnpm install + +# serve with hot reload at localhost:8080 +npm start + +# test projects +npm test + +# local build +npm run build +\`\`\` + + `, + ); + + return [[], file]; +} diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/abc.json.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/abc.json.ts new file mode 100644 index 000000000..b7523b5f9 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/abc.json.ts @@ -0,0 +1,28 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'abc', + 'json', + ` +{ + "name": "test", + "assets": { + "type": "command", + "command": { + "cmd": [ + "tnpm ii", + "tnpm run build" + ] + } + } +} + + `, + ); + + return [[], file]; +} + \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/build.json.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/build.json.ts new file mode 100644 index 000000000..08c1ab78a --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/build.json.ts @@ -0,0 +1,32 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'build', + 'json', + ` +{ + "entry": "src/index", + "alias": { + "@": "./src" + }, + "publicPath": "./", + "outputAssetsPath": { + "js": "", + "css": "" + }, + "plugins": [ + "build-plugin-react-app", + "@ali/build-plugin-recore-lowcode" + ], + "externals": { "react": "window.React", "react-dom": "window.ReactDOM", "@ali/recore": "window.Recore" } +} + + `, + ); + + 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 new file mode 100644 index 000000000..d8da2daaa --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/package.json.ts @@ -0,0 +1,66 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'package', + 'json', + ` +{ + "name": "test", + "version": "1.0.0", + "description": "test", + "scripts": { + "start": "build-scripts start", + "build": "build-scripts build", + "test": "build-scripts test" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">=6.1.0" + }, + "dependencies": { + "@ali/lowcode-runtime": "^0.8.0", + "@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": { + "@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" + }, + "lint-staged": { + "./src/**/*.{ts,tsx}": [ + "tslint --fix", + "git add" + ] + } +} + + `, + ); + + return [[], file]; +} + \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/public/index.html.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/public/index.html.ts new file mode 100644 index 000000000..af229a7e0 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/public/index.html.ts @@ -0,0 +1,51 @@ + +import ResultFile from '../../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'index', + 'html', + ` + + + + + + + lowcode-runtime-test + + + + + + + + + + + + + + + + `, + ); + + return [['public'], file]; +} + \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/app.ts.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/app.ts.ts new file mode 100644 index 000000000..784662e28 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/app.ts.ts @@ -0,0 +1,77 @@ + +import ResultFile from '../../../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'app', + 'ts', + ` +export default { + "sdkVersion": "1.0.3", + "history": "hash", // 浏览器路由:brower 哈希路由:hash + "containerId": "app", + "layout": { + "componentName": "BasicLayout", + "props": { + "navConfig": { + "showLanguageChange": true, + "data": [ + { + "hidden": false, + "navUuid": "FORM-CP5669B1-3AW9DCLHZAY8EIY6WE6X1-GFZM3V1K-6", + "children": [], + "icon": "", + "targetNew": false, + "title": "测试基础表格", + "inner": true, + "relateUuid": "FORM-CP5669B1-3AW9DCLHZAY8EIY6WE6X1-GFZM3V1K-6", + "slug": "qihfg" + }, + { + "hidden": false, + "navUuid": "FORM-CP5669B1-8AW9XCUT4PCH15SMDWUM3-ZPQP3V1K-1", + "children": [], + "icon": "", + "targetNew": false, + "title": "测试查询表格", + "inner": true, + "relateUuid": "zqhej", + "slug": "zqhej" + } + ], + "systemLink": "/my_dev_center_code/0.1.0", + "appName": "乐高转码demo", + "isFoldHorizontal": "n", + "showAppTitle": true, + "isFold": "n", + "searchBarType": "icon", + "singletons": {}, + "navTheme": "default", + "type": "top_side_fold", + "navStyle": "orange", + "layout": "auto", + "bgColor": "white", + "languageChangeUrl": "/common/account/changeAccountLanguage.json", + "showSearch": "n", + "openSubMode": false, + "showCrumb": true, + "isFixed": "y", + "showIcon": false, + "showNav": true + } + }, + }, + "theme": { + "package": "@alife/theme-fusion", + "version": "^0.1.0" + }, + "compDependencies": [] +} + + `, + ); + + return [['src','config'], file]; +} + \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/components.ts.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/components.ts.ts new file mode 100644 index 000000000..c894e92aa --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/components.ts.ts @@ -0,0 +1,43 @@ + +import ResultFile from '../../../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'components', + 'ts', + ` +/** + * 乐高组件 + */ +import Div from '@ali/vc-div/build/view'; +import Text from '@ali/vc-text/build/view'; +import Slot from '@ali/vc-slot/build/view'; +import Deep from '@ali/vc-deep/build/view'; +import Page from '@ali/vc-page/build/view'; +import Block from '@ali/vc-block/build/view'; + +const components = [Div, Text, Slot, Deep, Page, Block]; +const componentsMap = { +}; + +const processComponents = (deps) => { + deps.forEach((dep) => { + if (Array.isArray(dep)) { + processComponents(dep); + } else { + componentsMap[dep.displayName] = dep; + } + }); +}; + +processComponents(components); + +export default componentsMap; + + `, + ); + + return [['src','config'], file]; +} + \ No newline at end of file diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/utils.ts.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/utils.ts.ts new file mode 100644 index 000000000..84237ae1d --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/config/utils.ts.ts @@ -0,0 +1,29 @@ + +import ResultFile from '../../../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'utils', + 'ts', + ` +import toolkit from '@ali/vu-toolkit'; +import fusion from '@ali/vu-fusion'; +import dataSource from '@ali/vu-dataSource'; +import legaoBuiltin from '@ali/vu-legao-builtin'; +import formatter from '@ali/vu-formatter'; + +export default { + ...toolkit, + ...fusion, + legaoBuiltin, + dataSource, + formatter +} + + `, + ); + + return [['src','config'], file]; +} + \ No newline at end of file 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 new file mode 100644 index 000000000..979b85a20 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/index.ts.ts @@ -0,0 +1,101 @@ + +import ResultFile from '../../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'index', + '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, { + componentName: 'BasicLayout', +}); + +app.registerProvider(StaticRender); + +app.run(); + + `, + ); + + return [['src'], 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 new file mode 100644 index 000000000..78d1fff61 --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/src/router.ts.ts @@ -0,0 +1,21 @@ + +import ResultFile from '../../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'router', + 'ts', + ` +export default { + baseDir: './pages', + exact: true, + routes: [ + { main: './page_index', path: '/' }, + ], +}; + `, + ); + + return [['src'], file]; +} diff --git a/packages/code-generator/src/plugins/project/framework/recore/template/files/tsconfig.json.ts b/packages/code-generator/src/plugins/project/framework/recore/template/files/tsconfig.json.ts new file mode 100644 index 000000000..c30c3e46b --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/recore/template/files/tsconfig.json.ts @@ -0,0 +1,53 @@ + +import ResultFile from '../../../../../../model/ResultFile'; +import { IResultFile } from '../../../../../../types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + 'tsconfig', + 'json', + ` +{ + "compilerOptions": { + "lib": ["es2015", "dom"], + // Target latest version of ECMAScript. + "target": "esnext", + // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. + "module": "esnext", + // Search under node_modules for non-relative imports. + "moduleResolution": "node", + // Process & infer types from .js files. + "allowJs": true, + // Report errors in .js files. + "checkJs": false, + // Don't emit; allow Babel to transform files. + "noEmit": true, + // Enable strictest settings like strictNullChecks & noImplicitAny. + "strict": true, + // Allow default imports from modules with no default export. This does not affect code emit, just typechecking. + "allowSyntheticDefaultImports": true, + // Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. + "esModuleInterop": true, + // Specify JSX code generation: 'preserve', 'react-native', or 'react'. + "jsx": "preserve", + // Import emit helpers (e.g. __extends, __rest, etc..) from tslib + "importHelpers": true, + // Enables experimental support for ES7 decorators. + "experimentalDecorators": true, + // Generates corresponding .map file. + "sourceMap": true, + // Disallow inconsistently-cased references to the same file. + "forceConsistentCasingInFileNames": true, + // Allow json import + "resolveJsonModule": true, + // skip type checking of declaration files + "skipLibCheck": true, + } +} + + `, + ); + + return [[], 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 14b9ef773..cada1fd96 100644 --- a/packages/code-generator/src/plugins/project/i18n.ts +++ b/packages/code-generator/src/plugins/project/i18n.ts @@ -1,66 +1,69 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; -import { generateCompositeType } from '../../plugins/utils/compositeType'; +import { generateCompositeType } from '../../utils/compositeType'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IProjectInfo, } from '../../types'; -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; + + const ir = next.ir as IProjectInfo; + if (ir.i18n) { + const [, i18nStr] = generateCompositeType(ir.i18n); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileMainContent, + content: ` + const i18nConfig = ${i18nStr}; + let locale = 'en_US'; + + const changeLocale = (target) => { + locale = target; + }; + + const i18n = key => i18nConfig && i18nConfig[locale] && i18nConfig[locale][key] || ''; + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + ], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileExport, + content: ` + export { + changeLocale, + i18n, + }; + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + COMMON_CHUNK_NAME.FileMainContent, + ], + }); + } + + return next; }; - - const ir = next.ir as IProjectInfo; - if (ir.i18n) { - const [, i18nStr] = generateCompositeType(ir.i18n); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileMainContent, - content: ` - const i18nConfig = ${i18nStr}; - let locale = 'en_US'; - - const changeLocale = (target) => { - locale = target; - }; - - const i18n = key => i18nConfig && i18nConfig[locale] && i18nConfig[locale][key] || ''; - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - ], - }); - - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileExport, - content: ` - export { - changeLocale, - i18n, - }; - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - COMMON_CHUNK_NAME.FileMainContent, - ], - }); - } - - return next; + return plugin; }; -export default plugin; +export default pluginFactory; diff --git a/packages/code-generator/src/plugins/project/utils.ts b/packages/code-generator/src/plugins/project/utils.ts index f40ba41e2..32e7c5d17 100644 --- a/packages/code-generator/src/plugins/project/utils.ts +++ b/packages/code-generator/src/plugins/project/utils.ts @@ -2,59 +2,28 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; import { BuilderComponentPlugin, + BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct, IUtilInfo, } from '../../types'; -// TODO: How to merge this logic to common deps -const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { - const next: ICodeStruct = { - ...pre, - }; +const pluginFactory: BuilderComponentPluginFactory = () => { + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { + const next: ICodeStruct = { + ...pre, + }; - const ir = next.ir as IUtilInfo; - - if (ir.utils) { - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileExport, - content: ` - export default { - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - COMMON_CHUNK_NAME.FileMainContent, - ], - }); - - ir.utils.forEach(util => { - if (util.type === 'function') { - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileVarDefine, - content: ` - const ${util.name} = ${util.content}; - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - ], - }); - } + const ir = next.ir as IUtilInfo; + if (ir.utils) { next.chunks.push({ type: ChunkType.STRING, fileType: FileType.JS, name: COMMON_CHUNK_NAME.FileExport, content: ` - ${util.name}, + export default { `, linkAfter: [ COMMON_CHUNK_NAME.ExternalDepsImport, @@ -64,26 +33,60 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { COMMON_CHUNK_NAME.FileMainContent, ], }); - }); - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileExport, - content: ` - }; - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - COMMON_CHUNK_NAME.FileMainContent, - ], - }); - } + ir.utils.forEach(util => { + if (util.type === 'function') { + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileVarDefine, + content: ` + const ${util.name} = ${util.content}; + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + ], + }); + } - return next; + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileExport, + content: ` + ${util.name}, + `, + 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: ` + }; + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + COMMON_CHUNK_NAME.FileMainContent, + ], + }); + } + + return next; + }; + return plugin; }; -export default plugin; +export default pluginFactory; 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 947c535c2..519c2db5d 100644 --- a/packages/code-generator/src/postprocessor/prettier/index.ts +++ b/packages/code-generator/src/postprocessor/prettier/index.ts @@ -1,22 +1,42 @@ import prettier from 'prettier'; +import mypretter from '@ali/my-prettier'; -import { PostProcessor } from '../../types'; +import { PostProcessor, PostProcessorFactory } from '../../types'; const PARSERS = ['css', 'scss', 'less', 'json', 'html', 'vue']; -const codePrettier: PostProcessor = (content: string, fileType: string) => { - let parser: prettier.BuiltInParserName; - if (fileType === 'js' || fileType === 'jsx') { - parser = 'babel'; - } else if (PARSERS.indexOf(fileType) >= 0) { - parser = fileType as prettier.BuiltInParserName; - } else { - return content; - } +interface ProcessorConfig { + customFileTypeParser: Record; +} - return prettier.format(content, { - parser, - }); +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') { + 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; + } + + return prettier.format(content, { + parser, + }); + }; + + return codePrettier; }; -export default codePrettier; +export default factory; diff --git a/packages/code-generator/src/solutions/icejs.ts b/packages/code-generator/src/solutions/icejs.ts index 10fe842c4..62a514008 100644 --- a/packages/code-generator/src/solutions/icejs.ts +++ b/packages/code-generator/src/solutions/icejs.ts @@ -28,36 +28,40 @@ export default function createIceJsProjectBuilder(): IProjectBuilder { template, plugins: { components: [ - reactCommonDeps, - esmodule, - containerClass, - containerInjectUtils, - containerInitState, - containerLifeCycle, - containerMethod, - jsx, - css, + reactCommonDeps(), + esmodule({ + fileType: 'jsx', + }), + containerClass(), + containerInjectUtils(), + containerInitState(), + containerLifeCycle(), + containerMethod(), + jsx(), + css(), ], pages: [ - reactCommonDeps, - esmodule, - containerClass, - containerInjectUtils, - containerInitState, - containerLifeCycle, - containerMethod, - jsx, - css, + reactCommonDeps(), + esmodule({ + fileType: 'jsx', + }), + containerClass(), + containerInjectUtils(), + containerInitState(), + containerLifeCycle(), + containerMethod(), + jsx(), + css(), ], - router: [esmodule, iceJsRouter], - entry: [iceJsEntry], - constants: [constants], - utils: [esmodule, utils], - i18n: [i18n], - globalStyle: [iceJsGlobalStyle], - htmlEntry: [iceJsEntryHtml], - packageJSON: [iceJsPackageJSON], + router: [esmodule(), iceJsRouter()], + entry: [iceJsEntry()], + constants: [constants()], + utils: [esmodule(), utils()], + i18n: [i18n()], + globalStyle: [iceJsGlobalStyle()], + htmlEntry: [iceJsEntryHtml()], + packageJSON: [iceJsPackageJSON()], }, - postProcessors: [prettier], + postProcessors: [prettier()], }); } 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 804f4f3e6..a370d5641 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 { @@ -12,6 +14,8 @@ export enum FileType { HTML = 'html', JS = 'js', JSX = 'jsx', + TS = 'ts', + TSX = 'tsx', JSON = 'json', } @@ -32,7 +36,7 @@ export type CodeGeneratorFunction = (content: T) => string; export interface ICodeChunk { type: ChunkType; - fileType: FileType; + fileType: string; name: string; subModule?: string; content: ChunkContent; @@ -53,6 +57,8 @@ export type BuilderComponentPlugin = ( initStruct: ICodeStruct, ) => Promise; +export type BuilderComponentPluginFactory = (config?: T) => BuilderComponentPlugin; + export interface IChunkBuilder { run( ir: any, @@ -72,12 +78,13 @@ export interface ICompiledModule { } export interface IModuleBuilder { - generateModule: (input: unknown) => Promise; - linkCodeChunks: ( + generateModule(input: unknown): Promise; + generateModuleCode(schema: IBasicSchema | string): Promise; + linkCodeChunks( chunks: Record, fileName: string, - ) => IResultFile[]; - addPlugin: (plugin: BuilderComponentPlugin) => void; + ): IResultFile[]; + addPlugin(plugin: BuilderComponentPlugin): void; } /** @@ -99,11 +106,11 @@ export interface ICodeGenerator { export interface ISchemaParser { validate(schema: IBasicSchema): boolean; - parse(schema: IBasicSchema): IParseResult; + parse(schema: IBasicSchema | string): IParseResult; } export interface IProjectTemplate { - slots: IProjectSlots; + slots: Record; generateTemplate(): IResultDir; } @@ -112,39 +119,59 @@ 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 { - generateProject(schema: IProjectSchema): Promise; + generateProject(schema: IProjectSchema | string): Promise; } +export type PostProcessorFactory = (config?: T) => PostProcessor; export type PostProcessor = (content: string, fileType: string) => string; // TODO: temp interface, need modify 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[]; + +// export interface InteratorScope { +// [$item: string]: string; // $item 默认取值 "item" +// [$index: string]: string | number; // $index 默认取值 "index" +// __proto__: BlockInstance; +// } 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 f31bdeb09..68226e8ab 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 基本类型 @@ -87,10 +88,6 @@ export interface IUtilItem { content: IExternalDependency | IJSExpression; } -export interface IInlineStyle { - [cssAttribute: string]: string | number | IJSExpression; -} - export type ChildNodeItem = string | IJSExpression | IComponentNodeItem; export type ChildNodeType = ChildNodeItem | ChildNodeItem[]; @@ -106,9 +103,7 @@ export interface IComponentNodeItem { id?: string; componentName: string; // 组件名称 必填、首字母大写 props: { - className?: string; // 组件样式类名 - style?: IInlineStyle; // 组件内联样式 - [propName: string]: any; // 业务属性 + [propName: string]: CompositeValue; // 业务属性 }; // 组件属性对象 condition?: CompositeValue; // 渲染条件 loop?: CompositeValue; // 循环数据 @@ -124,15 +119,12 @@ 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; // 业务属性 - }; state?: { - [stateName: string]: any; // 容器初始数据 + [stateName: string]: CompositeValue; // 容器初始数据 }; - css: string; // 样式文件 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,在对应容器组件生成的 .jsx 文件中 import 引入; + css?: string; // 样式文件 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,在对应容器组件生成的 .jsx 文件中 import 引入; /** * LifeCycle * • constructor(props, context) @@ -144,37 +136,14 @@ export interface IContainerNodeItem extends IComponentNodeItem { * • componentWillUnmount() * • componentDidCatch(error, info) */ - lifeCycles?: { - constructor?: IJSExpression; - render?: IJSExpression; - componentDidMount?: IJSExpression; - componentDidUpdate?: IJSExpression; - componentWillUnmount?: IJSExpression; - componentDidCatch?: IJSExpression; - }; // 生命周期Hook方法 - methods?: { - [methodName: string]: IJSExpression; - }; // 自定义方法设置 - dataSource?: IDataSource; // 异步数据源配置 + lifeCycles?: Record; // 生命周期Hook方法 + methods?: Record; // 自定义方法设置 + dataSource?: { + list: IDataSourceConfig[]; + }; // 异步数据源配置 meta?: IBasicMeta | IPageMeta; } -/** - * 搭建基础协议 - 数据源 - * - * @export - * @interface IDataSource - */ -export interface IDataSource { - list: IDataSourceConfig[]; // 成为为单个请求配置 - /** - * 参数:为dataMap对象,key:数据id, value: 单个请求结果 - * 返回值:数据对象data,将会在渲染引擎和schemaToCode中通过调用this.setState(...)将返回的数据对象生效到state中; - * 支持返回一个Promise,通过resolve(返回数据),常用于串型发送请求场景,配合this.dataSourceMap[oneRequest.id].load()使用; - */ - dataHandler?: IJSExpression; -} - /** * 搭建基础协议 - 数据源单个配置 * @@ -184,7 +153,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 @@ -197,7 +166,7 @@ export interface IDataSourceConfig { * @interface IFetchOptions */ export interface IFetchOptions { - uri: string; // 请求地址 支持表达式 + url: string; // 请求地址 支持表达式 params?: { // 请求参数 [key: string]: any; @@ -209,6 +178,7 @@ export interface IFetchOptions { // 自定义请求头 [key: string]: string; }; + [extConfigName: string]: any; } export interface IBasicMeta { @@ -252,4 +222,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..a60351202 --- /dev/null +++ b/packages/code-generator/src/utils/nodeToJSX.ts @@ -0,0 +1,191 @@ +import { + ChildNodeType, + IComponentNodeItem, + 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 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 { props } = nodeItem; + let pieces: CodePiece[] = []; + + 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); +} diff --git a/packages/code-generator/tools/createTemplate.js b/packages/code-generator/tools/createTemplate.js new file mode 100644 index 000000000..b7568f7a9 --- /dev/null +++ b/packages/code-generator/tools/createTemplate.js @@ -0,0 +1,85 @@ +const fs = require("fs"); +const path = require('path'); + +console.log(process.argv); +let root = ''; +const pathArgIndex = process.argv.indexOf('--path'); +if (pathArgIndex >= 0) { + root = process.argv[pathArgIndex + 1]; +} + +if (!root) { + throw new Error('Can\'t find path argument'); +} + +function cloneStr(str, times) { + let count = times; + const arr = []; + while(count > 0) { + arr.push(str); + count -= 1; + } + return arr.join(''); +} + +function createTemplateFile(root, internalPath, fileName) { + //不是文件夹,则添加type属性为文件后缀名 + const fileTypeSrc = path.extname(fileName); + const fileType = fileTypeSrc.substring(1); + const baseName = path.basename(fileName, fileTypeSrc); + + const filePath = path.join(root, internalPath); + const pieces = filePath.split(path.sep); + const depPrefix = cloneStr('../', pieces.length - 1); + const content = fs.readFileSync(path.join(filePath, fileName), { + encoding: 'utf8', + }); + const pathList = (internalPath.split(path.sep) || []).filter(p => !!p); + const modulePathStr = JSON.stringify(pathList).replace(/\"/g, '\''); + + const templateContent = ` +import ResultFile from '${depPrefix}model/ResultFile'; +import { IResultFile } from '${depPrefix}types'; + +export default function getFile(): [string[], IResultFile] { + const file = new ResultFile( + '${baseName}', + '${fileType || ''}', + \` +${content} + \`, + ); + + return [${modulePathStr}, file]; +} + `; + + const targetFile = path.join(filePath, `${fileName}.ts`); + console.log(`=== Create File: ${targetFile}`); + fs.writeFileSync(targetFile, templateContent); +} + +function fileDisplay(root, internalPath) { + const dirPath = path.join(root, internalPath); + const filesList = fs.readdirSync(dirPath); + for(let i = 0; i < filesList.length; i++){ + //描述此文件/文件夹的对象 + const fileName = filesList[i]; + //拼接当前文件的路径(上一层路径+当前file的名字) + const filePath = path.join(dirPath, fileName); + //根据文件路径获取文件信息,返回一个fs.Stats对象 + const stats = fs.statSync(filePath); + if(stats.isDirectory()){ + //递归调用 + fileDisplay(root, path.join(internalPath, fileName)); + }else{ + createTemplateFile(root, internalPath, fileName); + } + } +} + +//调用函数遍历根目录,同时传递 文件夹路径和对应的数组 +//请使用同步读取 +fileDisplay(root, ''); +//读取完毕则写入到txt文件中 +// fs.writeFileSync('./data.txt', JSON.stringify(arr)); diff --git a/packages/code-generator/tsconfig.json b/packages/code-generator/tsconfig.json index eb8638577..7a6244f14 100644 --- a/packages/code-generator/tsconfig.json +++ b/packages/code-generator/tsconfig.json @@ -5,6 +5,7 @@ "lib": [ "es6" ], + "module": "commonjs", "types": ["node"], "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ }, diff --git a/packages/editor-skeleton/src/widget/panel.ts b/packages/editor-skeleton/src/widget/panel.ts index 6f9010dc0..263db6ae3 100644 --- a/packages/editor-skeleton/src/widget/panel.ts +++ b/packages/editor-skeleton/src/widget/panel.ts @@ -154,6 +154,9 @@ export default class Panel implements IWidget { } this.emitter.emit('activechange', true); } else if (this.inited) { + if (this.parent?.name && this.name.startsWith(this.parent.name)) { + this.inited = false; + } this._actived = false; this.parent?.unactive(this); this.emitter.emit('activechange', false);