Merge branch 'feat/code-generator' into 'release/0.9.0'

出码模块支持模块单独产出 & 开放自带插件

1. 模块生成器支持独立调用
2. Schema 输入支持 JSON string
3. 开放自带的 插件 与 工具集
4. 修改 demo 文件夹到 src 平行目录,精简 demo 代码

See merge request !874843
This commit is contained in:
康为 2020-07-01 11:13:31 +08:00
commit b772f226a6
70 changed files with 3072 additions and 1435 deletions

View File

@ -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();

View File

@ -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": "月飞"
}
}

View File

@ -8,17 +8,24 @@
], ],
"scripts": { "scripts": {
"build": "rimraf lib && tsc", "build": "rimraf lib && tsc",
"demo": "ts-node -r tsconfig-paths/register ./src/demo/main.ts", "demo": "node ./demo/demo.js",
"test": "ava" "test": "ava",
"template": "node ./tools/createTemplate.js"
}, },
"dependencies": { "dependencies": {
"@ali/am-eslint-config": "*", "@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", "@types/prettier": "^1.19.1",
"change-case": "^3.1.0", "change-case": "^3.1.0",
"prettier": "^2.0.2", "prettier": "^2.0.2",
"short-uuid": "^3.1.1" "short-uuid": "^3.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/babel__traverse": "^7.0.10",
"ava": "^1.0.1", "ava": "^1.0.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-loader": "^6.2.2", "ts-loader": "^6.2.2",

View File

@ -0,0 +1,6 @@
/// <reference types="node" />
declare module '@ali/my-prettier' {
function format(text: string, type: string): string;
export default format;
}

View File

@ -8,6 +8,107 @@ export const COMMON_CHUNK_NAME = {
StyleDepsImport: 'CommonStyleDepsImport', StyleDepsImport: 'CommonStyleDepsImport',
StyleCssContent: 'CommonStyleCssContent', StyleCssContent: 'CommonStyleCssContent',
HtmlContent: 'CommonHtmlContent', 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'; export const COMMON_SUB_MODULE_NAME = 'index';

View File

@ -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<any> {
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();

View File

@ -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;

View File

@ -62,11 +62,13 @@ export default class Builder implements ICodeBuilder {
} }
unprocessedChunks.splice(indexToRemove, 1); unprocessedChunks.splice(indexToRemove, 1);
if (!unprocessedChunks.some(ch => ch.name === name)) {
unprocessedChunks.forEach( unprocessedChunks.forEach(
// remove the processed chunk from all the linkAfter arrays from the remaining chunks // remove the processed chunk from all the linkAfter arrays from the remaining chunks
ch => (ch.linkAfter = ch.linkAfter.filter(after => after !== name)), ch => (ch.linkAfter = ch.linkAfter.filter(after => after !== name)),
); );
} }
}
return resultingString.join('\n'); return resultingString.join('\n');
} }

View File

@ -1,18 +1,24 @@
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
CodeGeneratorError, CodeGeneratorError,
IBasicSchema,
ICodeChunk, ICodeChunk,
ICompiledModule, ICompiledModule,
IModuleBuilder, IModuleBuilder,
IParseResult,
IResultDir,
IResultFile, IResultFile,
ISchemaParser,
PostProcessor, PostProcessor,
} from '../types'; } from '../types';
import { COMMON_SUB_MODULE_NAME } from '../const/generator'; import { COMMON_SUB_MODULE_NAME } from '../const/generator';
import SchemaParser from '../parser/SchemaParser';
import ChunkBuilder from './ChunkBuilder'; import ChunkBuilder from './ChunkBuilder';
import CodeBuilder from './CodeBuilder'; import CodeBuilder from './CodeBuilder';
import ResultDir from '../model/ResultDir';
import ResultFile from '../model/ResultFile'; import ResultFile from '../model/ResultFile';
export function createModuleBuilder( export function createModuleBuilder(
@ -66,6 +72,20 @@ export function createModuleBuilder(
}; };
}; };
const generateModuleCode = async (schema: IBasicSchema | string): Promise<IResultDir> => {
// 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 = ( const linkCodeChunks = (
chunks: Record<string, ICodeChunk[]>, chunks: Record<string, ICodeChunk[]>,
fileName: string, fileName: string,
@ -88,6 +108,7 @@ export function createModuleBuilder(
return { return {
generateModule, generateModule,
generateModuleCode,
linkCodeChunks, linkCodeChunks,
addPlugin: chunkGenerator.addPlugin.bind(chunkGenerator), addPlugin: chunkGenerator.addPlugin.bind(chunkGenerator),
}; };

View File

@ -57,8 +57,8 @@ export class ProjectBuilder implements IProjectBuilder {
this.postProcessors = postProcessors; this.postProcessors = postProcessors;
} }
public async generateProject(schema: IProjectSchema): Promise<IResultDir> { public async generateProject(schema: IProjectSchema | string): Promise<IResultDir> {
// Init working parts // Init
const schemaParser: ISchemaParser = new SchemaParser(); const schemaParser: ISchemaParser = new SchemaParser();
const builders = this.createModuleBuilders(); const builders = this.createModuleBuilders();
const projectRoot = this.template.generateTemplate(); const projectRoot = this.template.generateTemplate();
@ -90,7 +90,7 @@ export class ProjectBuilder implements IProjectBuilder {
const { files } = await builder.generateModule(containerInfo); const { files } = await builder.generateModule(containerInfo);
return { return {
moduleName: containerInfo.fileName, moduleName: containerInfo.moduleName,
path, path,
files, files,
}; };
@ -215,61 +215,19 @@ export class ProjectBuilder implements IProjectBuilder {
private createModuleBuilders(): Record<string, IModuleBuilder> { private createModuleBuilders(): Record<string, IModuleBuilder> {
const builders: Record<string, IModuleBuilder> = {}; const builders: Record<string, IModuleBuilder> = {};
builders.components = createModuleBuilder({ Object.keys(this.plugins).forEach(pluginName => {
plugins: this.plugins.components, if (this.plugins[pluginName].length > 0) {
postProcessors: this.postProcessors, const options: { mainFileName?: string } = {};
}); if (this.template.slots[pluginName] && this.template.slots[pluginName].fileName) {
builders.pages = createModuleBuilder({ options.mainFileName = this.template.slots[pluginName].fileName;
plugins: this.plugins.pages, }
postProcessors: this.postProcessors, builders[pluginName] = createModuleBuilder({
}); plugins: this.plugins[pluginName],
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, postProcessors: this.postProcessors,
...options,
}); });
} }
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; return builders;
} }

View File

@ -3,17 +3,78 @@
* *
*/ */
import { createProjectBuilder } from './generator/ProjectBuilder'; import { createProjectBuilder } from './generator/ProjectBuilder';
import { createModuleBuilder } from './generator/ModuleBuilder';
import { createDiskPublisher } from './publisher/disk'; import { createDiskPublisher } from './publisher/disk';
import createIceJsProjectBuilder from './solutions/icejs'; 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 * from './types';
export default { export default {
createProjectBuilder, createProjectBuilder,
createModuleBuilder,
solutions: { solutions: {
icejs: createIceJsProjectBuilder, icejs: createIceJsProjectBuilder,
recore: createRecoreProjectBuilder,
}, },
publishers: { publishers: {
disk: createDiskPublisher, 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,
},
}; };

View File

@ -5,7 +5,7 @@
import { SUPPORT_SCHEMA_VERSION_LIST } from '../const'; import { SUPPORT_SCHEMA_VERSION_LIST } from '../const';
import { handleChildren } from '../utils/children'; import { handleChildren } from '../utils/nodeToJSX';
import { import {
ChildNodeType, ChildNodeType,
@ -28,7 +28,8 @@ import {
const defaultContainer: IContainerInfo = { const defaultContainer: IContainerInfo = {
containerType: 'Component', containerType: 'Component',
componentName: 'Index', componentName: 'Component',
moduleName: 'Index',
fileName: 'Index', fileName: 'Index',
css: '', css: '',
props: {}, props: {},
@ -45,12 +46,23 @@ class SchemaParser implements ISchemaParser {
return true; return true;
} }
public parse(schema: IProjectSchema): IParseResult { public parse(schemaSrc: IProjectSchema | string): IParseResult {
// TODO: collect utils depends in JSExpression // TODO: collect utils depends in JSExpression
const compDeps: Record<string, IExternalDependency> = {}; const compDeps: Record<string, IExternalDependency> = {};
const internalDeps: Record<string, IInternalDependency> = {}; const internalDeps: Record<string, IInternalDependency> = {};
let utilsDeps: IExternalDependency[] = []; 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 => { schema.componentsMap.forEach(info => {
info.dependencyType = DependencyType.External; info.dependencyType = DependencyType.External;
@ -78,7 +90,7 @@ class SchemaParser implements ISchemaParser {
const container: IContainerInfo = { const container: IContainerInfo = {
...subRoot, ...subRoot,
containerType: subRoot.componentName, containerType: subRoot.componentName,
componentName: subRoot.fileName, moduleName: subRoot.fileName, // TODO: 驼峰化名称
}; };
return container; return container;
}); });
@ -104,9 +116,9 @@ class SchemaParser implements ISchemaParser {
const dep: IInternalDependency = { const dep: IInternalDependency = {
type, type,
moduleName: container.componentName, moduleName: container.moduleName,
destructuring: false, destructuring: false,
exportName: container.componentName, exportName: container.moduleName,
dependencyType: DependencyType.Internal, dependencyType: DependencyType.Internal,
}; };
@ -131,9 +143,15 @@ class SchemaParser implements ISchemaParser {
.filter(container => container.containerType === 'Page') .filter(container => container.containerType === 'Page')
.map(page => { .map(page => {
const meta = page.meta as IPageMeta; const meta = page.meta as IPageMeta;
if (meta) {
return { return {
path: meta.router, path: meta.router,
componentName: page.componentName, componentName: page.moduleName,
};
}
return {
path: '',
componentName: page.moduleName,
}; };
}); });

View File

@ -2,6 +2,7 @@ import { COMMON_CHUNK_NAME } from '../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
CodeGeneratorError, CodeGeneratorError,
DependencyType, DependencyType,
@ -41,7 +42,7 @@ function groupDepsByPack(deps: IDependency[]): Record<string, IDependency[]> {
function buildPackageImport( function buildPackageImport(
pkg: string, pkg: string,
deps: IDependency[], deps: IDependency[],
isJSX: boolean, targetFileType: string,
): ICodeChunk[] { ): ICodeChunk[] {
const chunks: ICodeChunk[] = []; const chunks: ICodeChunk[] = [];
let defaultImport: string = ''; let defaultImport: string = '';
@ -58,7 +59,7 @@ function buildPackageImport(
if (dep.subName) { if (dep.subName) {
chunks.push({ chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: isJSX ? FileType.JSX : FileType.JS, fileType: targetFileType,
name: COMMON_CHUNK_NAME.FileVarDefine, name: COMMON_CHUNK_NAME.FileVarDefine,
content: `const ${targetName} = ${srcName}.${dep.subName};`, content: `const ${targetName} = ${srcName}.${dep.subName};`,
linkAfter: [ linkAfter: [
@ -103,7 +104,7 @@ function buildPackageImport(
statementL.push(`'@/${(deps[0] as IInternalDependency).type}/${pkg}';`); statementL.push(`'@/${(deps[0] as IInternalDependency).type}/${pkg}';`);
chunks.push({ chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: isJSX ? FileType.JSX : FileType.JS, fileType: targetFileType,
name: COMMON_CHUNK_NAME.InternalDepsImport, name: COMMON_CHUNK_NAME.InternalDepsImport,
content: statementL.join(' '), content: statementL.join(' '),
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
@ -112,7 +113,7 @@ function buildPackageImport(
statementL.push(`'${pkg}';`); statementL.push(`'${pkg}';`);
chunks.push({ chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: isJSX ? FileType.JSX : FileType.JS, fileType: targetFileType,
name: COMMON_CHUNK_NAME.ExternalDepsImport, name: COMMON_CHUNK_NAME.ExternalDepsImport,
content: statementL.join(' '), content: statementL.join(' '),
linkAfter: [], linkAfter: [],
@ -122,25 +123,36 @@ function buildPackageImport(
return chunks; return chunks;
} }
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { type PluginConfig = {
fileType: string;
}
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?: PluginConfig) => {
const cfg: PluginConfig = {
fileType: FileType.JS,
...config,
};
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
const isJSX = next.chunks.some(chunk => chunk.fileType === FileType.JSX);
const ir = next.ir as IWithDependency; const ir = next.ir as IWithDependency;
if (ir && ir.deps && ir.deps.length > 0) { if (ir && ir.deps && ir.deps.length > 0) {
const packs = groupDepsByPack(ir.deps); const packs = groupDepsByPack(ir.deps);
Object.keys(packs).forEach(pkg => { Object.keys(packs).forEach(pkg => {
const chunks = buildPackageImport(pkg, packs[pkg], isJSX); const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType);
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;

View File

@ -2,13 +2,15 @@ import { COMMON_CHUNK_NAME } from '../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
} from '../../types'; } from '../../types';
// TODO: How to merge this logic to common deps // TODO: How to merge this logic to common deps
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -22,6 +24,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -1,15 +1,8 @@
export const REACT_CHUNK_NAME = { export const REACT_CHUNK_NAME = {
ClassStart: 'ReactComponentClassDefineStart',
ClassEnd: 'ReactComponentClassDefineEnd',
ClassLifeCycle: 'ReactComponentClassMemberLifeCycle',
ClassMethod: 'ReactComponentClassMemberMethod',
ClassRenderStart: 'ReactComponentClassRenderStart', ClassRenderStart: 'ReactComponentClassRenderStart',
ClassRenderPre: 'ReactComponentClassRenderPre', ClassRenderPre: 'ReactComponentClassRenderPre',
ClassRenderEnd: 'ReactComponentClassRenderEnd', ClassRenderEnd: 'ReactComponentClassRenderEnd',
ClassRenderJSX: 'ReactComponentClassRenderJSX', ClassRenderJSX: 'ReactComponentClassRenderJSX',
ClassConstructorStart: 'ReactComponentClassConstructorStart',
ClassConstructorEnd: 'ReactComponentClassConstructorEnd',
ClassConstructorContent: 'ReactComponentClassConstructorContent',
ClassDidMountStart: 'ReactComponentClassDidMountStart', ClassDidMountStart: 'ReactComponentClassDidMountStart',
ClassDidMountEnd: 'ReactComponentClassDidMountEnd', ClassDidMountEnd: 'ReactComponentClassDidMountEnd',
ClassDidMountContent: 'ReactComponentClassDidMountContent', ClassDidMountContent: 'ReactComponentClassDidMountContent',

View File

@ -1,15 +1,17 @@
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 { REACT_CHUNK_NAME } from './const';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IContainerInfo, IContainerInfo,
} from '../../../types'; } from '../../../types';
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -19,8 +21,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: FileType.JSX,
name: REACT_CHUNK_NAME.ClassStart, name: CLASS_DEFINE_CHUNK_NAME.Start,
content: `class ${ir.componentName} extends React.Component {`, content: `class ${ir.moduleName} extends React.Component {`,
linkAfter: [ linkAfter: [
COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.ExternalDepsImport,
COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport,
@ -32,27 +34,27 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: FileType.JSX,
name: REACT_CHUNK_NAME.ClassEnd, name: CLASS_DEFINE_CHUNK_NAME.End,
content: `}`, content: `}`,
linkAfter: [REACT_CHUNK_NAME.ClassStart, REACT_CHUNK_NAME.ClassRenderEnd], linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start, REACT_CHUNK_NAME.ClassRenderEnd],
}); });
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: FileType.JSX,
name: REACT_CHUNK_NAME.ClassConstructorStart, name: CLASS_DEFINE_CHUNK_NAME.ConstructorStart,
content: 'constructor(props, context) { super(props); ', content: 'constructor(props, context) { super(props); ',
linkAfter: [REACT_CHUNK_NAME.ClassStart], linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start],
}); });
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: FileType.JSX,
name: REACT_CHUNK_NAME.ClassConstructorEnd, name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd,
content: '}', content: '}',
linkAfter: [ linkAfter: [
REACT_CHUNK_NAME.ClassConstructorStart, CLASS_DEFINE_CHUNK_NAME.ConstructorStart,
REACT_CHUNK_NAME.ClassConstructorContent, CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
], ],
}); });
@ -62,10 +64,9 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
name: REACT_CHUNK_NAME.ClassRenderStart, name: REACT_CHUNK_NAME.ClassRenderStart,
content: 'render() {', content: 'render() {',
linkAfter: [ linkAfter: [
REACT_CHUNK_NAME.ClassStart, CLASS_DEFINE_CHUNK_NAME.Start,
REACT_CHUNK_NAME.ClassConstructorEnd, CLASS_DEFINE_CHUNK_NAME.ConstructorEnd,
REACT_CHUNK_NAME.ClassLifeCycle, CLASS_DEFINE_CHUNK_NAME.InsMethod,
REACT_CHUNK_NAME.ClassMethod,
], ],
}); });
@ -85,17 +86,19 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: FileType.JSX,
name: COMMON_CHUNK_NAME.FileExport, name: COMMON_CHUNK_NAME.FileExport,
content: `export default ${ir.componentName};`, content: `export default ${ir.moduleName};`,
linkAfter: [ linkAfter: [
COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.ExternalDepsImport,
COMMON_CHUNK_NAME.InternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport,
COMMON_CHUNK_NAME.FileVarDefine, COMMON_CHUNK_NAME.FileVarDefine,
COMMON_CHUNK_NAME.FileUtilDefine, COMMON_CHUNK_NAME.FileUtilDefine,
REACT_CHUNK_NAME.ClassEnd, CLASS_DEFINE_CHUNK_NAME.End,
], ],
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -1,16 +1,27 @@
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 { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IContainerInfo, IContainerInfo,
} from '../../../types'; } from '../../../types';
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { type PluginConfig = {
fileType: string;
}
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
const cfg: PluginConfig = {
fileType: FileType.JSX,
...config,
};
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -26,14 +37,16 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.fileType,
name: REACT_CHUNK_NAME.ClassConstructorContent, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
content: `this.state = { ${fields.join('')} };`, content: `this.state = { ${fields.join('')} };`,
linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart],
}); });
} }
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -1,16 +1,29 @@
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 { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IContainerInfo, IContainerInfo,
} from '../../../types'; } from '../../../types';
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { type PluginConfig = {
fileType: string;
implementType: 'inConstructor' | 'insMember' | 'hooks';
}
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
const cfg: PluginConfig = {
fileType: FileType.JSX,
implementType: 'inConstructor',
...config,
};
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -24,16 +37,28 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
return `${stateName}: ${isString ? `'${value}'` : value},`; return `${stateName}: ${isString ? `'${value}'` : value},`;
}); });
if (cfg.implementType === 'inConstructor') {
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.fileType,
name: REACT_CHUNK_NAME.ClassConstructorContent, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
content: `this.state = { ${fields.join('')} };`, content: `this.state = { ${fields.join('')} };`,
linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], 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 next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -1,26 +1,39 @@
import { REACT_CHUNK_NAME } from './const'; import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
} from '../../../types'; } from '../../../types';
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { type PluginConfig = {
fileType: string;
}
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
const cfg: PluginConfig = {
fileType: FileType.JSX,
...config,
};
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.fileType,
name: REACT_CHUNK_NAME.ClassConstructorContent, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
content: `this.utils = utils;`, content: `this.utils = utils;`,
linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart],
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -1,12 +1,14 @@
import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
import { REACT_CHUNK_NAME } from './const'; import { REACT_CHUNK_NAME } from './const';
import { import {
getFuncExprBody, getFuncExprBody,
transformFuncExpr2MethodMember, transformFuncExpr2MethodMember,
} from '../../utils/jsExpression'; } from '../../../utils/jsExpression';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
CodeGeneratorError, CodeGeneratorError,
FileType, FileType,
@ -16,7 +18,21 @@ import {
IJSExpression, IJSExpression,
} from '../../../types'; } from '../../../types';
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { type PluginConfig = {
fileType: string;
exportNameMapping: Record<string, string>;
normalizeNameMapping: Record<string, string>;
}
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
const cfg: PluginConfig = {
fileType: FileType.JSX,
exportNameMapping: {},
normalizeNameMapping: {},
...config,
};
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -26,21 +42,23 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
if (ir.lifeCycles) { if (ir.lifeCycles) {
const lifeCycles = ir.lifeCycles; const lifeCycles = ir.lifeCycles;
const chunks = Object.keys(lifeCycles).map<ICodeChunk>(lifeCycleName => { const chunks = Object.keys(lifeCycles).map<ICodeChunk>(lifeCycleName => {
if (lifeCycleName === 'constructor') { const normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName;
const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName;
if (normalizeName === 'constructor') {
return { return {
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.fileType,
name: REACT_CHUNK_NAME.ClassConstructorContent, name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
content: getFuncExprBody( content: getFuncExprBody(
(lifeCycles[lifeCycleName] as IJSExpression).value, (lifeCycles[lifeCycleName] as IJSExpression).value,
), ),
linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart], linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]],
}; };
} }
if (lifeCycleName === 'render') { if (normalizeName === 'render') {
return { return {
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.fileType,
name: REACT_CHUNK_NAME.ClassRenderPre, name: REACT_CHUNK_NAME.ClassRenderPre,
content: getFuncExprBody( content: getFuncExprBody(
(lifeCycles[lifeCycleName] as IJSExpression).value, (lifeCycles[lifeCycleName] as IJSExpression).value,
@ -48,34 +66,25 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
linkAfter: [REACT_CHUNK_NAME.ClassRenderStart], linkAfter: [REACT_CHUNK_NAME.ClassRenderStart],
}; };
} }
if (
lifeCycleName === 'componentDidMount' ||
lifeCycleName === 'componentDidUpdate' ||
lifeCycleName === 'componentWillUnmount' ||
lifeCycleName === 'componentDidCatch'
) {
return { return {
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.fileType,
name: REACT_CHUNK_NAME.ClassLifeCycle, name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
content: transformFuncExpr2MethodMember( content: transformFuncExpr2MethodMember(
lifeCycleName, exportName,
(lifeCycles[lifeCycleName] as IJSExpression).value, (lifeCycles[lifeCycleName] as IJSExpression).value,
), ),
linkAfter: [ linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
REACT_CHUNK_NAME.ClassStart,
REACT_CHUNK_NAME.ClassConstructorEnd,
],
}; };
}
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;

View File

@ -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 { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeChunk, ICodeChunk,
@ -12,7 +13,17 @@ import {
IJSExpression, IJSExpression,
} from '../../../types'; } from '../../../types';
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { type PluginConfig = {
fileType: string;
}
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
const cfg: PluginConfig = {
fileType: FileType.JSX,
...config,
};
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -23,23 +34,21 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const methods = ir.methods; const methods = ir.methods;
const chunks = Object.keys(methods).map<ICodeChunk>(methodName => ({ const chunks = Object.keys(methods).map<ICodeChunk>(methodName => ({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.fileType,
name: REACT_CHUNK_NAME.ClassMethod, name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
content: transformFuncExpr2MethodMember( content: transformFuncExpr2MethodMember(
methodName, methodName,
(methods[methodName] as IJSExpression).value, (methods[methodName] as IJSExpression).value,
), ),
linkAfter: [ linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
REACT_CHUNK_NAME.ClassStart,
REACT_CHUNK_NAME.ClassConstructorEnd,
REACT_CHUNK_NAME.ClassLifeCycle,
],
})); }));
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;

View File

@ -1,140 +1,39 @@
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
ChildNodeItem, BuilderComponentPluginFactory,
ChildNodeType,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IComponentNodeItem,
IContainerInfo, IContainerInfo,
IInlineStyle,
IJSExpression,
} from '../../../types'; } from '../../../types';
import { handleChildren } from '../../../utils/children';
import { generateCompositeType } from '../../utils/compositeType';
import { REACT_CHUNK_NAME } from './const'; import { REACT_CHUNK_NAME } from './const';
function generateInlineStyle(style: IInlineStyle): string | null { import { createReactNodeGenerator } from '../../../utils/nodeToJSX';
const attrLines = Object.keys(style).map((cssAttribute: string) => {
const [isString, valueStr] = generateCompositeType(style[cssAttribute]);
const valuePart = isString ? `'${valueStr}'` : valueStr;
return `${cssAttribute}: ${valuePart},`;
});
if (attrLines.length === 0) { type PluginConfig = {
return null; fileType: string;
}
return `{ ${attrLines.join('')} }`;
} }
function generateAttr(attrName: string, attrValue: any): string { const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
if (attrName === 'initValue' || attrName === 'labelCol') { const cfg: PluginConfig = {
return ''; fileType: FileType.JSX,
} ...config,
const [isString, valueStr] = generateCompositeType(attrValue); };
return `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`;
}
function mapNodeName(src: string): string { const generator = createReactNodeGenerator();
if (src === 'Div') {
return 'div';
}
return src;
}
function generateNode(nodeItem: IComponentNodeItem): string { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
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(`</${mapNodeName(nodeItem.componentName)}>`);
} 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<string>(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 = { const next: ICodeStruct = {
...pre, ...pre,
}; };
const ir = next.ir as IContainerInfo; const ir = next.ir as IContainerInfo;
const jsxContent = generator(ir);
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 = `(<React.Fragment>${childrenCode.join(
'',
)}</React.Fragment>)`;
}
}
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.fileType,
name: REACT_CHUNK_NAME.ClassRenderJSX, name: REACT_CHUNK_NAME.ClassRenderJSX,
content: `return ${jsxContent};`, content: `return ${jsxContent};`,
linkAfter: [ linkAfter: [
@ -144,6 +43,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -2,14 +2,15 @@ import { COMMON_CHUNK_NAME } from '../../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IContainerInfo, IContainerInfo,
} from '../../../types'; } from '../../../types';
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -23,6 +24,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -0,0 +1,3 @@
export const RECORE_CHUNK_NAME = {
};

View File

@ -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<unknown> = () => {
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<string, CompositeValue>)[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;

View File

@ -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<unknown> = () => {
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;

View File

@ -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<unknown> = () => {
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;

View File

@ -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<unknown> = () => {
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;

View File

@ -0,0 +1,29 @@
import { COMMON_CHUNK_NAME } from '../../../const/generator';
import {
BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType,
ICodeStruct,
} from '../../../types';
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = {
...pre,
};
next.chunks.push({
type: ChunkType.STRING,
fileType: 'vx',
name: COMMON_CHUNK_NAME.CustomContent,
content: `<div {...globalProps.div} className="recore-loading" x-if={this.__loading} />`,
linkAfter: [],
});
return next;
};
return plugin;
};
export default pluginFactory;

View File

@ -2,13 +2,26 @@ import { COMMON_CHUNK_NAME } from '../../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IContainerInfo, IContainerInfo,
} from '../../../types'; } from '../../../types';
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { type PluginConfig = {
fileType: string;
moduleFileType: string;
}
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
const cfg: PluginConfig = {
fileType: FileType.CSS,
moduleFileType: FileType.JSX,
...config,
};
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -17,7 +30,7 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.CSS, fileType: cfg.fileType,
name: COMMON_CHUNK_NAME.StyleCssContent, name: COMMON_CHUNK_NAME.StyleCssContent,
content: ir.css, content: ir.css,
linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
@ -25,13 +38,15 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
next.chunks.push({ next.chunks.push({
type: ChunkType.STRING, type: ChunkType.STRING,
fileType: FileType.JSX, fileType: cfg.moduleFileType,
name: COMMON_CHUNK_NAME.InternalDepsImport, name: COMMON_CHUNK_NAME.InternalDepsImport,
content: `import './index.css';`, content: `import './index.${cfg.fileType}';`,
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -1,15 +1,16 @@
import { COMMON_CHUNK_NAME } from '../../const/generator'; import { COMMON_CHUNK_NAME } from '../../const/generator';
import { generateCompositeType } from '../../plugins/utils/compositeType'; import { generateCompositeType } from '../../utils/compositeType';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IProjectInfo, IProjectInfo,
} from '../../types'; } from '../../types';
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -49,6 +50,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
} }
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -2,14 +2,15 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IProjectInfo, IProjectInfo,
} from '../../../../../types'; } from '../../../../../types';
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -50,6 +51,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -2,14 +2,15 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IProjectInfo, IProjectInfo,
} from '../../../../../types'; } from '../../../../../types';
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -38,6 +39,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -2,14 +2,15 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IProjectInfo, IProjectInfo,
} from '../../../../../types'; } from '../../../../../types';
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -48,6 +49,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -2,6 +2,7 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
@ -20,8 +21,8 @@ interface IIceJsPackageJSON extends IPackageJSON {
originTemplate: string; originTemplate: string;
} }
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -81,6 +82,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -2,14 +2,15 @@ import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IRouterInfo, IRouterInfo,
} from '../../../../../types'; } from '../../../../../types';
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -74,6 +75,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
}); });
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -2,8 +2,8 @@ import ResultDir from '../../../../../model/ResultDir';
import { import {
IProjectTemplate, IProjectTemplate,
IResultDir, IResultDir,
IResultFile,
} from '../../../../../types'; } from '../../../../../types';
import { runFileGenerator } from '../../../../../utils/templateHelper';
import file12 from './files/abc.json'; import file12 from './files/abc.json';
import file11 from './files/build.json'; import file11 from './files/build.json';
@ -26,29 +26,6 @@ import file3 from './files/stylelintignore';
import file2 from './files/stylelintrc.js'; import file2 from './files/stylelintrc.js';
import file1 from './files/tsconfig.json'; 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 = { const icejsTemplate: IProjectTemplate = {
slots: { slots: {
components: { components: {

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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',
`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, maximum-scale=1.0, user-scalable=no" />
<title>lowcode-runtime-test</title>
<link rel="shortcut icon" type="image/png" href="https://img.alicdn.com/tfs/TB1zgoCemrqK1RjSZK9XXXyypXa-96-96.png" />
<link rel="stylesheet" id="" href="//g.alicdn.com/legao-comp/csxs/1.0.2/web.css?t=1f">
<script
src="https://g.alicdn.com/code/lib/??react/16.9.0/umd/react.production.min.js,react-dom/16.9.0/umd/react-dom.production.min.js,prop-types/15.7.2/prop-types.js"></script>
<!-- React 非压缩版代码,可根据需要替换或通过代理替换后方便调试 -->
<!-- <script src="https://g.alicdn.com/code/lib/??react/16.9.0/umd/react.development.js,react-dom/16.9.0/umd/react-dom.development.js,prop-types/15.7.2/prop-types.js"></script> -->
<script src="https://g.alicdn.com/mylib/@ali/recore/1.6.8/umd/recore.min.js"></script>
<script>
React.PropTypes = PropTypes;
</script>
<style type="text/css">
body {
-webkit-overflow-scrolling: touch;
}
</style>
</head>
<body>
<script>
window.g_config = {
appKey: 'test', // 乐高应用的 AppKey
// isSectionalRender: true, // 必填,标记当前为局部使用
// autoRender: true,
// index: 'search_form',
};
</script>
</body>
</html>
`,
);
return [['public'], file];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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;

View File

@ -1,15 +1,16 @@
import { COMMON_CHUNK_NAME } from '../../const/generator'; import { COMMON_CHUNK_NAME } from '../../const/generator';
import { generateCompositeType } from '../../plugins/utils/compositeType'; import { generateCompositeType } from '../../utils/compositeType';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IProjectInfo, IProjectInfo,
} from '../../types'; } from '../../types';
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -61,6 +62,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
} }
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -2,14 +2,15 @@ import { COMMON_CHUNK_NAME } from '../../const/generator';
import { import {
BuilderComponentPlugin, BuilderComponentPlugin,
BuilderComponentPluginFactory,
ChunkType, ChunkType,
FileType, FileType,
ICodeStruct, ICodeStruct,
IUtilInfo, IUtilInfo,
} from '../../types'; } from '../../types';
// TODO: How to merge this logic to common deps const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const next: ICodeStruct = { const next: ICodeStruct = {
...pre, ...pre,
}; };
@ -84,6 +85,8 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
} }
return next; return next;
};
return plugin;
}; };
export default plugin; export default pluginFactory;

View File

@ -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];
}

View File

@ -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'
);
}

View File

@ -1,15 +1,32 @@
import prettier from 'prettier'; 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 PARSERS = ['css', 'scss', 'less', 'json', 'html', 'vue'];
const codePrettier: PostProcessor = (content: string, fileType: string) => { interface ProcessorConfig {
customFileTypeParser: Record<string, string>;
}
const factory: PostProcessorFactory<ProcessorConfig> = (config?: ProcessorConfig) => {
const cfg: ProcessorConfig = {
customFileTypeParser: {},
...config,
};
const codePrettier: PostProcessor = (content: string, fileType: string) => {
let parser: prettier.BuiltInParserName; let parser: prettier.BuiltInParserName;
if (fileType === 'js' || fileType === 'jsx') { if (fileType === 'js' || fileType === 'jsx') {
parser = 'babel'; parser = 'babel';
} else if (fileType === 'ts' || fileType === 'tsx') {
parser = 'typescript';
} else if (PARSERS.indexOf(fileType) >= 0) { } else if (PARSERS.indexOf(fileType) >= 0) {
parser = fileType as prettier.BuiltInParserName; 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 { } else {
return content; return content;
} }
@ -17,6 +34,9 @@ const codePrettier: PostProcessor = (content: string, fileType: string) => {
return prettier.format(content, { return prettier.format(content, {
parser, parser,
}); });
};
return codePrettier;
}; };
export default codePrettier; export default factory;

View File

@ -28,36 +28,40 @@ export default function createIceJsProjectBuilder(): IProjectBuilder {
template, template,
plugins: { plugins: {
components: [ components: [
reactCommonDeps, reactCommonDeps(),
esmodule, esmodule({
containerClass, fileType: 'jsx',
containerInjectUtils, }),
containerInitState, containerClass(),
containerLifeCycle, containerInjectUtils(),
containerMethod, containerInitState(),
jsx, containerLifeCycle(),
css, containerMethod(),
jsx(),
css(),
], ],
pages: [ pages: [
reactCommonDeps, reactCommonDeps(),
esmodule, esmodule({
containerClass, fileType: 'jsx',
containerInjectUtils, }),
containerInitState, containerClass(),
containerLifeCycle, containerInjectUtils(),
containerMethod, containerInitState(),
jsx, containerLifeCycle(),
css, containerMethod(),
jsx(),
css(),
], ],
router: [esmodule, iceJsRouter], router: [esmodule(), iceJsRouter()],
entry: [iceJsEntry], entry: [iceJsEntry()],
constants: [constants], constants: [constants()],
utils: [esmodule, utils], utils: [esmodule(), utils()],
i18n: [i18n], i18n: [i18n()],
globalStyle: [iceJsGlobalStyle], globalStyle: [iceJsGlobalStyle()],
htmlEntry: [iceJsEntryHtml], htmlEntry: [iceJsEntryHtml()],
packageJSON: [iceJsPackageJSON], packageJSON: [iceJsPackageJSON()],
}, },
postProcessors: [prettier], postProcessors: [prettier()],
}); });
} }

View File

@ -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()],
});
}

View File

@ -4,6 +4,8 @@ import {
IProjectSchema, IProjectSchema,
IResultDir, IResultDir,
IResultFile, IResultFile,
IComponentNodeItem,
IJSExpression,
} from './index'; } from './index';
export enum FileType { export enum FileType {
@ -12,6 +14,8 @@ export enum FileType {
HTML = 'html', HTML = 'html',
JS = 'js', JS = 'js',
JSX = 'jsx', JSX = 'jsx',
TS = 'ts',
TSX = 'tsx',
JSON = 'json', JSON = 'json',
} }
@ -32,7 +36,7 @@ export type CodeGeneratorFunction<T> = (content: T) => string;
export interface ICodeChunk { export interface ICodeChunk {
type: ChunkType; type: ChunkType;
fileType: FileType; fileType: string;
name: string; name: string;
subModule?: string; subModule?: string;
content: ChunkContent; content: ChunkContent;
@ -53,6 +57,8 @@ export type BuilderComponentPlugin = (
initStruct: ICodeStruct, initStruct: ICodeStruct,
) => Promise<ICodeStruct>; ) => Promise<ICodeStruct>;
export type BuilderComponentPluginFactory<T> = (config?: T) => BuilderComponentPlugin;
export interface IChunkBuilder { export interface IChunkBuilder {
run( run(
ir: any, ir: any,
@ -72,12 +78,13 @@ export interface ICompiledModule {
} }
export interface IModuleBuilder { export interface IModuleBuilder {
generateModule: (input: unknown) => Promise<ICompiledModule>; generateModule(input: unknown): Promise<ICompiledModule>;
linkCodeChunks: ( generateModuleCode(schema: IBasicSchema | string): Promise<IResultDir>;
linkCodeChunks(
chunks: Record<string, ICodeChunk[]>, chunks: Record<string, ICodeChunk[]>,
fileName: string, fileName: string,
) => IResultFile[]; ): IResultFile[];
addPlugin: (plugin: BuilderComponentPlugin) => void; addPlugin(plugin: BuilderComponentPlugin): void;
} }
/** /**
@ -99,11 +106,11 @@ export interface ICodeGenerator {
export interface ISchemaParser { export interface ISchemaParser {
validate(schema: IBasicSchema): boolean; validate(schema: IBasicSchema): boolean;
parse(schema: IBasicSchema): IParseResult; parse(schema: IBasicSchema | string): IParseResult;
} }
export interface IProjectTemplate { export interface IProjectTemplate {
slots: IProjectSlots; slots: Record<string, IProjectSlot>;
generateTemplate(): IResultDir; generateTemplate(): IResultDir;
} }
@ -112,39 +119,59 @@ export interface IProjectSlot {
fileName?: string; fileName?: string;
} }
export interface IProjectSlots { // export interface IProjectSlots {
components: IProjectSlot; // components: IProjectSlot;
pages: IProjectSlot; // pages: IProjectSlot;
router: IProjectSlot; // router: IProjectSlot;
entry: IProjectSlot; // entry: IProjectSlot;
constants?: IProjectSlot; // constants?: IProjectSlot;
utils?: IProjectSlot; // utils?: IProjectSlot;
i18n?: IProjectSlot; // i18n?: IProjectSlot;
globalStyle: IProjectSlot; // globalStyle: IProjectSlot;
htmlEntry: IProjectSlot; // htmlEntry: IProjectSlot;
packageJSON: IProjectSlot; // packageJSON: IProjectSlot;
} // }
export interface IProjectPlugins { export interface IProjectPlugins {
components: BuilderComponentPlugin[]; [slotName: string]: BuilderComponentPlugin[];
pages: BuilderComponentPlugin[];
router: BuilderComponentPlugin[];
entry: BuilderComponentPlugin[];
constants?: BuilderComponentPlugin[];
utils?: BuilderComponentPlugin[];
i18n?: BuilderComponentPlugin[];
globalStyle: BuilderComponentPlugin[];
htmlEntry: BuilderComponentPlugin[];
packageJSON: BuilderComponentPlugin[];
} }
export interface IProjectBuilder { export interface IProjectBuilder {
generateProject(schema: IProjectSchema): Promise<IResultDir>; generateProject(schema: IProjectSchema | string): Promise<IResultDir>;
} }
export type PostProcessorFactory<T> = (config?: T) => PostProcessor;
export type PostProcessor = (content: string, fileType: string) => string; export type PostProcessor = (content: string, fileType: string) => string;
// TODO: temp interface, need modify // TODO: temp interface, need modify
export interface IPluginOptions { export interface IPluginOptions {
fileDirDepth: number; 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<T> {
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;
// }

View File

@ -17,8 +17,8 @@ export interface IParseResult {
} }
export interface IContainerInfo extends IContainerNodeItem, IWithDependency { export interface IContainerInfo extends IContainerNodeItem, IWithDependency {
componentName: string;
containerType: string; containerType: string;
moduleName: string;
} }
export interface IWithDependency { export interface IWithDependency {

View File

@ -10,6 +10,7 @@ import { IExternalDependency } from './index';
export interface IJSExpression { export interface IJSExpression {
type: 'JSExpression'; type: 'JSExpression';
value: string; value: string;
[extConfigName: string]: any;
} }
// JSON 基本类型 // JSON 基本类型
@ -87,10 +88,6 @@ export interface IUtilItem {
content: IExternalDependency | IJSExpression; content: IExternalDependency | IJSExpression;
} }
export interface IInlineStyle {
[cssAttribute: string]: string | number | IJSExpression;
}
export type ChildNodeItem = string | IJSExpression | IComponentNodeItem; export type ChildNodeItem = string | IJSExpression | IComponentNodeItem;
export type ChildNodeType = ChildNodeItem | ChildNodeItem[]; export type ChildNodeType = ChildNodeItem | ChildNodeItem[];
@ -106,9 +103,7 @@ export interface IComponentNodeItem {
id?: string; id?: string;
componentName: string; // 组件名称 必填、首字母大写 componentName: string; // 组件名称 必填、首字母大写
props: { props: {
className?: string; // 组件样式类名 [propName: string]: CompositeValue; // 业务属性
style?: IInlineStyle; // 组件内联样式
[propName: string]: any; // 业务属性
}; // 组件属性对象 }; // 组件属性对象
condition?: CompositeValue; // 渲染条件 condition?: CompositeValue; // 渲染条件
loop?: CompositeValue; // 循环数据 loop?: CompositeValue; // 循环数据
@ -124,15 +119,12 @@ export interface IComponentNodeItem {
* @extends {IComponentNodeItem} * @extends {IComponentNodeItem}
*/ */
export interface IContainerNodeItem extends IComponentNodeItem { export interface IContainerNodeItem extends IComponentNodeItem {
componentName: string; // 'Page' | 'Block' | 'Component' 组件类型 必填、首字母大写 componentName: 'Page' | 'Block' | 'Component'; // 'Page' | 'Block' | 'Component' 组件类型 必填、首字母大写
fileName: string; // 文件名称 必填、英文 fileName: string; // 文件名称 必填、英文
defaultProps?: {
[propName: string]: any; // 业务属性
};
state?: { state?: {
[stateName: string]: any; // 容器初始数据 [stateName: string]: CompositeValue; // 容器初始数据
}; };
css: string; // 样式文件 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,在对应容器组件生成的 .jsx 文件中 import 引入; css?: string; // 样式文件 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,在对应容器组件生成的 .jsx 文件中 import 引入;
/** /**
* LifeCycle * LifeCycle
* constructor(props, context) * constructor(props, context)
@ -144,37 +136,14 @@ export interface IContainerNodeItem extends IComponentNodeItem {
* componentWillUnmount() * componentWillUnmount()
* componentDidCatch(error, info) * componentDidCatch(error, info)
*/ */
lifeCycles?: { lifeCycles?: Record<string, IJSExpression>; // 生命周期Hook方法
constructor?: IJSExpression; methods?: Record<string, IJSExpression>; // 自定义方法设置
render?: IJSExpression; dataSource?: {
componentDidMount?: IJSExpression; list: IDataSourceConfig[];
componentDidUpdate?: IJSExpression; }; // 异步数据源配置
componentWillUnmount?: IJSExpression;
componentDidCatch?: IJSExpression;
}; // 生命周期Hook方法
methods?: {
[methodName: string]: IJSExpression;
}; // 自定义方法设置
dataSource?: IDataSource; // 异步数据源配置
meta?: IBasicMeta | IPageMeta; meta?: IBasicMeta | IPageMeta;
} }
/**
* -
*
* @export
* @interface IDataSource
*/
export interface IDataSource {
list: IDataSourceConfig[]; // 成为为单个请求配置
/**
* dataMap对象key:数据id, value: 单个请求结果
* dataschemaToCode中通过调用this.setState(...)state中
* Promiseresolve()this.dataSourceMap[oneRequest.id].load()使
*/
dataHandler?: IJSExpression;
}
/** /**
* - * -
* *
@ -184,7 +153,7 @@ export interface IDataSource {
export interface IDataSourceConfig { export interface IDataSourceConfig {
id: string; // 数据请求ID标识 id: string; // 数据请求ID标识
isInit: boolean; // 是否为初始数据 支持表达式 值为true时将在组件初始化渲染时自动发送当前数据请求 isInit: boolean; // 是否为初始数据 支持表达式 值为true时将在组件初始化渲染时自动发送当前数据请求
type: 'fetch' | 'mtop' | 'jsonp' | 'custom' | 'doServer'; // 数据请求类型 type: string; // 数据请求类型 'fetch' | 'mtop' | 'jsonp' | 'custom'
requestHandler?: IJSExpression; // 自定义扩展的外部请求处理器 仅type='custom'时生效 requestHandler?: IJSExpression; // 自定义扩展的外部请求处理器 仅type='custom'时生效
options?: IFetchOptions; // 请求参数配置 每种请求类型对应不同参数 options?: IFetchOptions; // 请求参数配置 每种请求类型对应不同参数
dataHandler?: IJSExpression; // 数据结果处理函数,形如:(data, err) => Object dataHandler?: IJSExpression; // 数据结果处理函数,形如:(data, err) => Object
@ -197,7 +166,7 @@ export interface IDataSourceConfig {
* @interface IFetchOptions * @interface IFetchOptions
*/ */
export interface IFetchOptions { export interface IFetchOptions {
uri: string; // 请求地址 支持表达式 url: string; // 请求地址 支持表达式
params?: { params?: {
// 请求参数 // 请求参数
[key: string]: any; [key: string]: any;
@ -209,6 +178,7 @@ export interface IFetchOptions {
// 自定义请求头 // 自定义请求头
[key: string]: string; [key: string]: string;
}; };
[extConfigName: string]: any;
} }
export interface IBasicMeta { export interface IBasicMeta {
@ -252,4 +222,5 @@ export interface IAppMeta {
description?: string; // 应用描述 description?: string; // 应用描述
spma?: string; // 应用spma A位信息 spma?: string; // 应用spma A位信息
creator?: string; // author creator?: string; // author
[otherAttrName: string]: any;
} }

View File

@ -1,35 +0,0 @@
import {
ChildNodeItem,
ChildNodeType,
IComponentNodeItem,
IJSExpression,
} from '../types';
// tslint:disable-next-line: no-empty
const noop = () => [];
export function handleChildren<T>(
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);
}
}

View File

@ -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;
}

View File

@ -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'
);
}

View File

@ -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<T>(
children: ChildNodeType,
handlers: HandlerSet<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);
}
}
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}</${tagName}>${afterParts}`;
}
return `${beforeParts}<${tagName}${attrsParts} />${afterParts}`;
}
export function createNodeGenerator(handlers: HandlerSet<string>, 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<string>(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,
]);
}

View File

@ -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);
}

View File

@ -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));

View File

@ -5,6 +5,7 @@
"lib": [ "lib": [
"es6" "es6"
], ],
"module": "commonjs",
"types": ["node"], "types": ["node"],
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */ "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
}, },