From 16f8cf2afedaf006985ec1b2ab7dc2f2be768d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=A5=E5=B8=8C?= Date: Mon, 20 Jul 2020 22:45:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20merge=20newest=20cod?= =?UTF-8?q?e=20from=20feature=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code-generator/demo/demo.js | 103 +++++++++++++++++- .../code-generator/demo/shenmaSample.json | 36 ++++++ packages/code-generator/package.json | 4 +- packages/code-generator/src/index.ts | 25 +++++ .../plugins/project/framework/icejs/index.ts | 17 +++ .../src/postprocessor/prettier/index.ts | 2 +- .../src/publisher/disk/index.ts | 31 ++---- .../code-generator/src/publisher/zip/index.ts | 69 ++++++++++++ .../code-generator/src/publisher/zip/utils.ts | 60 ++++++++++ .../code-generator/src/solutions/icejs.ts | 20 ++-- packages/code-generator/src/types/error.ts | 7 ++ packages/code-generator/src/types/index.ts | 1 + .../code-generator/src/types/publisher.ts | 19 ++++ .../code-generator/src/utils/nodeToJSX.ts | 4 +- 14 files changed, 361 insertions(+), 37 deletions(-) create mode 100644 packages/code-generator/demo/shenmaSample.json create mode 100644 packages/code-generator/src/plugins/project/framework/icejs/index.ts create mode 100644 packages/code-generator/src/publisher/zip/index.ts create mode 100644 packages/code-generator/src/publisher/zip/utils.ts create mode 100644 packages/code-generator/src/types/publisher.ts diff --git a/packages/code-generator/demo/demo.js b/packages/code-generator/demo/demo.js index 91e145de9..61c71de97 100644 --- a/packages/code-generator/demo/demo.js +++ b/packages/code-generator/demo/demo.js @@ -1,4 +1,5 @@ const fs = require('fs'); +// ../lib 可以替换成 @ali/lowcode-code-generator const CodeGenerator = require('../lib').default; function flatFiles(rootName, dir) { @@ -50,4 +51,104 @@ function main() { }); } -main(); +function exportModule() { + const schemaJson = fs.readFileSync('./demo/shenmaSample.json', { encoding: 'utf8' }); + const moduleBuilder = CodeGenerator.createModuleBuilder({ + plugins: [ + CodeGenerator.plugins.react.reactCommonDeps(), + CodeGenerator.plugins.common.esmodule({ + fileType: 'jsx', + }), + CodeGenerator.plugins.react.containerClass(), + CodeGenerator.plugins.react.containerInitState(), + CodeGenerator.plugins.react.containerLifeCycle(), + CodeGenerator.plugins.react.containerMethod(), + CodeGenerator.plugins.react.jsx(), + CodeGenerator.plugins.style.css(), + ], + postProcessors: [ + CodeGenerator.postprocessor.prettier(), + ], + mainFileName: 'index', + }); + + moduleBuilder.generateModuleCode(schemaJson).then(result => { + displayResultInConsole(result); + return result; + }); +} + +function exportProject() { + const schemaJson = fs.readFileSync('./demo/sampleSchema.json', { encoding: 'utf8' }); + + const builder = CodeGenerator.createProjectBuilder({ + template: CodeGenerator.solutionParts.icejs.template, + plugins: { + components: [ + CodeGenerator.plugins.react.reactCommonDeps(), + CodeGenerator.plugins.common.esmodule({ + fileType: 'jsx', + }), + CodeGenerator.plugins.react.containerClass(), + CodeGenerator.plugins.react.containerInitState(), + CodeGenerator.plugins.react.containerLifeCycle(), + CodeGenerator.plugins.react.containerMethod(), + CodeGenerator.plugins.react.jsx(), + CodeGenerator.plugins.style.css(), + ], + pages: [ + CodeGenerator.plugins.react.reactCommonDeps(), + CodeGenerator.plugins.common.esmodule({ + fileType: 'jsx', + }), + CodeGenerator.plugins.react.containerClass(), + CodeGenerator.plugins.react.containerInitState(), + CodeGenerator.plugins.react.containerLifeCycle(), + CodeGenerator.plugins.react.containerMethod(), + CodeGenerator.plugins.react.jsx(), + CodeGenerator.plugins.style.css(), + ], + router: [ + CodeGenerator.plugins.common.esmodule(), + CodeGenerator.solutionParts.icejs.plugins.router(), + ], + entry: [ + CodeGenerator.solutionParts.icejs.plugins.entry(), + ], + constants: [ + CodeGenerator.plugins.project.constants(), + ], + utils: [ + CodeGenerator.plugins.common.esmodule(), + CodeGenerator.plugins.project.utils(), + ], + i18n: [ + CodeGenerator.plugins.project.i18n(), + ], + globalStyle: [ + CodeGenerator.solutionParts.icejs.plugins.globalStyle(), + ], + htmlEntry: [ + CodeGenerator.solutionParts.icejs.plugins.entryHtml(), + ], + packageJSON: [ + CodeGenerator.solutionParts.icejs.plugins.packageJSON(), + ], + }, + postProcessors: [ + CodeGenerator.postprocessor.prettier(), + ], + }); + + builder.generateProject(schemaJson).then(result => { + displayResultInConsole(result); + writeResultToDisk(result, 'output/lowcodeDemo').then(response => + console.log('Write to disk: ', JSON.stringify(response)), + ); + return result; + }); +} + +// main(); +// exportModule(); +exportProject(); diff --git a/packages/code-generator/demo/shenmaSample.json b/packages/code-generator/demo/shenmaSample.json new file mode 100644 index 000000000..eed4c1031 --- /dev/null +++ b/packages/code-generator/demo/shenmaSample.json @@ -0,0 +1,36 @@ +{ + "version": "1.0.0", + "componentsMap": [ + { + "componentName": "Demo", + "package": "@ali/demo", + "version": "1.19.18", + "destructuring": true, + "exportName": "Demo" + } + ], + "id": "page_kc326r8m", + "componentsTree": [{ + "componentName": "Page", + "id": "node_kc326r8h", + "props": {}, + "condition": true, + "loopArgs": ["item", "index"], + "children": [{ + "componentName": "Demo", + "id": "node_kc326r8i", + "props": { + "value": "文本内容", + "color": "#ffffff", + "ui_maxLine": 2, + "url": "", + "ui_type": "xs", + "style": {}, + "className": "" + }, + "condition": true, + "loopArgs": ["item", "index"] + }] + }], + "params": {} +} diff --git a/packages/code-generator/package.json b/packages/code-generator/package.json index 28121c879..bfe83ccf4 100644 --- a/packages/code-generator/package.json +++ b/packages/code-generator/package.json @@ -4,7 +4,8 @@ "description": "出码引擎 for LowCode Engine", "main": "lib/index.js", "files": [ - "lib" + "lib", + "demo" ], "scripts": { "build": "rimraf lib && tsc", @@ -21,6 +22,7 @@ "@babel/types": "^7.9.5", "@types/prettier": "^1.19.1", "change-case": "^3.1.0", + "jszip": "^3.5.0", "prettier": "^2.0.2", "short-uuid": "^3.1.1" }, diff --git a/packages/code-generator/src/index.ts b/packages/code-generator/src/index.ts index 9de9c1538..5d670b5ab 100644 --- a/packages/code-generator/src/index.ts +++ b/packages/code-generator/src/index.ts @@ -5,11 +5,17 @@ import { createProjectBuilder } from './generator/ProjectBuilder'; import { createModuleBuilder } from './generator/ModuleBuilder'; import { createDiskPublisher } from './publisher/disk'; +import { createZipPublisher } from './publisher/zip'; import createIceJsProjectBuilder from './solutions/icejs'; import createRecoreProjectBuilder from './solutions/recore'; // 引入说明 import { REACT_CHUNK_NAME } from './plugins/component/react/const'; +import { + COMMON_CHUNK_NAME, + CLASS_DEFINE_CHUNK_NAME, + DEFAULT_LINK_AFTER, +} from './const/generator'; // 引入通用插件组 import esmodule from './plugins/common/esmodule'; @@ -26,6 +32,7 @@ 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 prettier from './postprocessor/prettier'; // 引入常用工具 import * as utilsCommon from './utils/common'; @@ -34,6 +41,9 @@ import * as utilsJsExpression from './utils/jsExpression'; import * as utilsNodeToJSX from './utils/nodeToJSX'; import * as utilsTemplateHelper from './utils/templateHelper'; +// 引入内置解决方案模块 +import icejs from './plugins/project/framework/icejs'; + export * from './types'; export default { @@ -43,8 +53,12 @@ export default { icejs: createIceJsProjectBuilder, recore: createRecoreProjectBuilder, }, + solutionParts: { + icejs, + }, publishers: { disk: createDiskPublisher, + zip: createZipPublisher, }, plugins: { common: { @@ -70,6 +84,9 @@ export default { utils, }, }, + postprocessor: { + prettier, + }, utils: { common: utilsCommon, compositeType: utilsCompositeType, @@ -77,4 +94,12 @@ export default { nodeToJSX: utilsNodeToJSX, templateHelper: utilsTemplateHelper, }, + chunkNames: { + COMMON_CHUNK_NAME, + CLASS_DEFINE_CHUNK_NAME, + REACT_CHUNK_NAME, + }, + defaultLinkAfter: { + COMMON_DEFAULT_LINK_AFTER: DEFAULT_LINK_AFTER, + }, }; diff --git a/packages/code-generator/src/plugins/project/framework/icejs/index.ts b/packages/code-generator/src/plugins/project/framework/icejs/index.ts new file mode 100644 index 000000000..e9c8f255f --- /dev/null +++ b/packages/code-generator/src/plugins/project/framework/icejs/index.ts @@ -0,0 +1,17 @@ +import template from './template'; +import entry from './plugins/entry'; +import entryHtml from './plugins/entryHtml'; +import globalStyle from './plugins/globalStyle'; +import packageJSON from './plugins/packageJSON'; +import router from './plugins/router'; + +export default { + template, + plugins: { + entry, + entryHtml, + globalStyle, + packageJSON, + router, + }, +}; diff --git a/packages/code-generator/src/postprocessor/prettier/index.ts b/packages/code-generator/src/postprocessor/prettier/index.ts index 519c2db5d..85c2f9741 100644 --- a/packages/code-generator/src/postprocessor/prettier/index.ts +++ b/packages/code-generator/src/postprocessor/prettier/index.ts @@ -5,7 +5,7 @@ import { PostProcessor, PostProcessorFactory } from '../../types'; const PARSERS = ['css', 'scss', 'less', 'json', 'html', 'vue']; -interface ProcessorConfig { +type ProcessorConfig = { customFileTypeParser: Record; } diff --git a/packages/code-generator/src/publisher/disk/index.ts b/packages/code-generator/src/publisher/disk/index.ts index 77f45c07a..b63649ab9 100644 --- a/packages/code-generator/src/publisher/disk/index.ts +++ b/packages/code-generator/src/publisher/disk/index.ts @@ -1,21 +1,10 @@ -import { CodeGeneratorError, IResultDir } from '../../types'; - -export type PublisherFactory = (configuration?: Partial) => U; - -export interface IPublisher { - publish: (options?: T) => Promise>; - getProject: () => IResultDir | void; - setProject: (project: IResultDir) => void; -} - -export interface IPublisherFactoryParams { - project?: IResultDir; -} -export interface IPublisherResponse { - success: boolean; - payload?: T; -} - +import { + IResultDir, + PublisherFactory, + IPublisher, + IPublisherFactoryParams, + PublisherError, +} from '../../types'; import { writeFolder } from './utils'; export interface IDiskFactoryParams extends IPublisherFactoryParams { @@ -37,7 +26,7 @@ export const createDiskPublisher: PublisherFactory< const getProject = (): IResultDir => { if (!project) { - throw new CodeGeneratorError('Missing Project'); + throw new PublisherError('Missing Project'); } return project; }; @@ -55,7 +44,7 @@ export const createDiskPublisher: PublisherFactory< const publish = async (options: IDiskFactoryParams = {}) => { const projectToPublish = options.project || project; if (!projectToPublish) { - throw new CodeGeneratorError('Missing Project'); + throw new PublisherError('Missing Project'); } const projectOutputPath = options.outputPath || outputPath; @@ -75,7 +64,7 @@ export const createDiskPublisher: PublisherFactory< ); return { success: true, payload: projectOutputPath }; } catch (error) { - throw new CodeGeneratorError(error); + throw new PublisherError(error); } }; diff --git a/packages/code-generator/src/publisher/zip/index.ts b/packages/code-generator/src/publisher/zip/index.ts new file mode 100644 index 000000000..c400c6849 --- /dev/null +++ b/packages/code-generator/src/publisher/zip/index.ts @@ -0,0 +1,69 @@ +import { + IResultDir, + PublisherFactory, + IPublisher, + IPublisherFactoryParams, + PublisherError, +} from '../../types'; +import { isNodeProcess, writeZipToDisk, generateProjectZip } from './utils' + +// export type ZipBuffer = Buffer | Blob; +export type ZipBuffer = Buffer; + +declare type ZipPublisherResponse = string | ZipBuffer; + +export interface ZipFactoryParams extends IPublisherFactoryParams { + outputPath?: string; + projectSlug?: string; +} + +export interface ZipPublisher extends IPublisher { + getOutputPath: () => string | undefined; + setOutputPath: (path: string) => void; +} + +export const createZipPublisher: PublisherFactory = ( + params: ZipFactoryParams = {}, +): ZipPublisher => { + let { project, outputPath } = params; + + const getProject = () => project; + const setProject = (projectToSet: IResultDir) => { + project = projectToSet; + } + + const getOutputPath = () => outputPath; + const setOutputPath = (path: string) => { + outputPath = path; + } + + const publish = async (options: ZipFactoryParams = {}) => { + const projectToPublish = options.project || project; + if (!projectToPublish) { + throw new PublisherError('MissingProject'); + } + + const zipName = options.projectSlug || params.projectSlug || projectToPublish.name; + + try { + const zipContent = await generateProjectZip(projectToPublish); + + // If not output path is provided, zip is not written to disk + const projectOutputPath = options.outputPath || outputPath; + if (projectOutputPath && isNodeProcess()) { + await writeZipToDisk(projectOutputPath, zipContent, zipName); + } + return { success: true, payload: zipContent }; + } catch (error) { + throw new PublisherError(error); + } + } + + return { + publish, + getProject, + setProject, + getOutputPath, + setOutputPath, + }; +} diff --git a/packages/code-generator/src/publisher/zip/utils.ts b/packages/code-generator/src/publisher/zip/utils.ts new file mode 100644 index 000000000..b7e1100b8 --- /dev/null +++ b/packages/code-generator/src/publisher/zip/utils.ts @@ -0,0 +1,60 @@ +import JSZip from 'jszip'; +import { IResultDir, IResultFile } from '../../types'; +import { ZipBuffer } from './index'; + +export const isNodeProcess = (): boolean => { + return ( + typeof process === 'object' && + typeof process.versions === 'object' && + typeof process.versions.node !== 'undefined' + ); +} + +export const writeZipToDisk = ( + zipFolderPath: string, + content: ZipBuffer, + zipName: string, +): void => { + const fs = require('fs'); + const path = require('path'); + + if (!fs.existsSync(zipFolderPath)) { + fs.mkdirSync(zipFolderPath, { recursive: true }); + } + + const zipPath = path.join(zipFolderPath, `${zipName}.zip`); + + const writeStream = fs.createWriteStream(zipPath); + writeStream.write(content); + writeStream.end(); +} + +export const generateProjectZip = async (project: IResultDir): Promise => { + let zip = new JSZip(); + zip = writeFolderToZip(project, zip, true); + // const zipType = isNodeProcess() ? 'nodebuffer' : 'blob'; + const zipType = 'nodebuffer'; // 目前先只支持 node 调用 + return zip.generateAsync({ type: zipType }); +} + +const writeFolderToZip = ( + folder: IResultDir, + parentFolder: JSZip, + ignoreFolder: boolean = false, +) => { + const zipFolder = ignoreFolder ? parentFolder : parentFolder.folder(folder.name); + if (zipFolder !== null) { + folder.files.forEach((file: IResultFile) => { + // const options = file.contentEncoding === 'base64' ? { base64: true } : {}; + const options = {}; + const fileName = file.ext ? `${file.name}.${file.ext}` : file.name; + zipFolder.file(fileName, file.content, options); + }); + + folder.dirs.forEach((subFolder: IResultDir) => { + writeFolderToZip(subFolder, zipFolder); + }); + } + + return parentFolder; +} diff --git a/packages/code-generator/src/solutions/icejs.ts b/packages/code-generator/src/solutions/icejs.ts index 62a514008..bfb7e28f8 100644 --- a/packages/code-generator/src/solutions/icejs.ts +++ b/packages/code-generator/src/solutions/icejs.ts @@ -12,20 +12,16 @@ 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 iceJsEntry from '../plugins/project/framework/icejs/plugins/entry'; -import iceJsEntryHtml from '../plugins/project/framework/icejs/plugins/entryHtml'; -import iceJsGlobalStyle from '../plugins/project/framework/icejs/plugins/globalStyle'; -import iceJsPackageJSON from '../plugins/project/framework/icejs/plugins/packageJSON'; -import iceJsRouter from '../plugins/project/framework/icejs/plugins/router'; -import template from '../plugins/project/framework/icejs/template'; import i18n from '../plugins/project/i18n'; import utils from '../plugins/project/utils'; +import icejs from '../plugins/project/framework/icejs'; + import { prettier } from '../postprocessor'; export default function createIceJsProjectBuilder(): IProjectBuilder { return createProjectBuilder({ - template, + template: icejs.template, plugins: { components: [ reactCommonDeps(), @@ -53,14 +49,14 @@ export default function createIceJsProjectBuilder(): IProjectBuilder { jsx(), css(), ], - router: [esmodule(), iceJsRouter()], - entry: [iceJsEntry()], + router: [esmodule(), icejs.plugins.router()], + entry: [icejs.plugins.entry()], constants: [constants()], utils: [esmodule(), utils()], i18n: [i18n()], - globalStyle: [iceJsGlobalStyle()], - htmlEntry: [iceJsEntryHtml()], - packageJSON: [iceJsPackageJSON()], + globalStyle: [icejs.plugins.globalStyle()], + htmlEntry: [icejs.plugins.entryHtml()], + packageJSON: [icejs.plugins.packageJSON()], }, postProcessors: [prettier()], }); diff --git a/packages/code-generator/src/types/error.ts b/packages/code-generator/src/types/error.ts index b58e9fe90..8c0393a88 100644 --- a/packages/code-generator/src/types/error.ts +++ b/packages/code-generator/src/types/error.ts @@ -18,3 +18,10 @@ export class CompatibilityError extends CodeGeneratorError { super(errorString); } } + +// tslint:disable-next-line: max-classes-per-file +export class PublisherError extends CodeGeneratorError { + constructor(errorString: string) { + super(errorString); + } +} diff --git a/packages/code-generator/src/types/index.ts b/packages/code-generator/src/types/index.ts index 1938ed5f5..102515d43 100644 --- a/packages/code-generator/src/types/index.ts +++ b/packages/code-generator/src/types/index.ts @@ -4,3 +4,4 @@ export * from './error'; export * from './result'; export * from './schema'; export * from './intermediate'; +export * from './publisher'; diff --git a/packages/code-generator/src/types/publisher.ts b/packages/code-generator/src/types/publisher.ts new file mode 100644 index 000000000..a632e4d9b --- /dev/null +++ b/packages/code-generator/src/types/publisher.ts @@ -0,0 +1,19 @@ +import { + IResultDir, +} from './index'; + +export type PublisherFactory = (configuration?: Partial) => U; + +export interface IPublisher { + publish: (options?: T) => Promise>; + getProject: () => IResultDir | void; + setProject: (project: IResultDir) => void; +} + +export interface IPublisherFactoryParams { + project?: IResultDir; +} +export interface IPublisherResponse { + success: boolean; + payload?: T; +} diff --git a/packages/code-generator/src/utils/nodeToJSX.ts b/packages/code-generator/src/utils/nodeToJSX.ts index a60351202..db5f87c1d 100644 --- a/packages/code-generator/src/utils/nodeToJSX.ts +++ b/packages/code-generator/src/utils/nodeToJSX.ts @@ -96,8 +96,10 @@ export function generateReactCtrlLine(nodeItem: IComponentNodeItem): CodePiece[] } if (nodeItem.condition) { + const [isString, value] = generateCompositeType(nodeItem.condition); + pieces.unshift({ - value: `(${generateCompositeType(nodeItem.condition)}) && (`, + value: `(${isString ? `'${value}'` : value}) && (`, type: PIECE_TYPE.BEFORE, }); pieces.push({