mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-02-21 16:30:32 +00:00
Merge branch 'feat/code-generator' into release/331
This commit is contained in:
commit
93a4e82a91
@ -1 +1,9 @@
|
|||||||
出码模块
|
# 出码模块
|
||||||
|
|
||||||
|
## 安装接入
|
||||||
|
|
||||||
|
|
||||||
|
## 自定义导出
|
||||||
|
|
||||||
|
|
||||||
|
## 开始开发
|
||||||
|
|||||||
39
packages/code-generator/package.json
Normal file
39
packages/code-generator/package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "@ali/lowcode-engine-code-generator",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "出码引擎 for LowCode Engine",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "rimraf lib && tsc",
|
||||||
|
"demo": "ts-node -r tsconfig-paths/register ./src/demo/main.ts",
|
||||||
|
"test": "ava"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ali/am-eslint-config": "*",
|
||||||
|
"change-case": "^3.1.0",
|
||||||
|
"short-uuid": "^3.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"ava": "^1.0.1",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"ts-node": "^7.0.1",
|
||||||
|
"tsconfig-paths": "^3.9.0"
|
||||||
|
},
|
||||||
|
"ava": {
|
||||||
|
"compileEnhancements": false,
|
||||||
|
"snapshotDir": "test/fixtures/__snapshots__",
|
||||||
|
"extensions": [
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"require": [
|
||||||
|
"ts-node/register"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "http://registry.npm.alibaba-inc.com"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
13
packages/code-generator/src/const/generator.ts
Normal file
13
packages/code-generator/src/const/generator.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const COMMON_CHUNK_NAME = {
|
||||||
|
ExternalDepsImport: 'CommonExternalDependencyImport',
|
||||||
|
InternalDepsImport: 'CommonInternalDependencyImport',
|
||||||
|
FileVarDefine: 'CommonFileScopeVarDefine',
|
||||||
|
FileUtilDefine: 'CommonFileScopeMethodDefine',
|
||||||
|
FileMainContent: 'CommonFileMainContent',
|
||||||
|
FileExport: 'CommonFileExport',
|
||||||
|
StyleDepsImport: 'CommonStyleDepsImport',
|
||||||
|
StyleCssContent: 'CommonStyleCssContent',
|
||||||
|
HtmlContent: 'CommonHtmlContent',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const COMMON_SUB_MODULE_NAME = 'index';
|
||||||
9
packages/code-generator/src/const/index.ts
Normal file
9
packages/code-generator/src/const/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const NATIVE_ELE_PKG: string = 'native';
|
||||||
|
|
||||||
|
export const CONTAINER_TYPE = {
|
||||||
|
COMPONENT: 'Component',
|
||||||
|
BLOCK: 'Block',
|
||||||
|
PAGE: 'Page',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SUPPORT_SCHEMA_VERSION_LIST = ['0.0.1'];
|
||||||
53
packages/code-generator/src/demo/main.ts
Normal file
53
packages/code-generator/src/demo/main.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
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();
|
||||||
239
packages/code-generator/src/demo/simpleDemo.ts
Normal file
239
packages/code-generator/src/demo/simpleDemo.ts
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import { IProjectSchema } from '@/types';
|
||||||
|
|
||||||
|
// meta: {
|
||||||
|
// title: '测试',
|
||||||
|
// router: '/',
|
||||||
|
// },
|
||||||
|
|
||||||
|
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;
|
||||||
74
packages/code-generator/src/generator/ChunkBuilder.ts
Normal file
74
packages/code-generator/src/generator/ChunkBuilder.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
IChunkBuilder,
|
||||||
|
ICodeChunk,
|
||||||
|
ICodeStruct,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
import { COMMON_SUB_MODULE_NAME } from '../const/generator';
|
||||||
|
|
||||||
|
export const groupChunks = (chunks: ICodeChunk[]): ICodeChunk[][] => {
|
||||||
|
const col = chunks.reduce(
|
||||||
|
(chunksSet: Record<string, ICodeChunk[]>, chunk) => {
|
||||||
|
const fileKey = `${chunk.subModule || COMMON_SUB_MODULE_NAME}.${
|
||||||
|
chunk.fileType
|
||||||
|
}`;
|
||||||
|
if (!chunksSet[fileKey]) {
|
||||||
|
chunksSet[fileKey] = [];
|
||||||
|
}
|
||||||
|
chunksSet[fileKey].push(chunk);
|
||||||
|
return chunksSet;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.keys(col).map(key => col[key]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码片段构建器
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class ChunkBuilder
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
export default class ChunkBuilder implements IChunkBuilder {
|
||||||
|
private plugins: BuilderComponentPlugin[];
|
||||||
|
|
||||||
|
constructor(plugins: BuilderComponentPlugin[] = []) {
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(
|
||||||
|
ir: unknown,
|
||||||
|
initialStructure: ICodeStruct = {
|
||||||
|
ir,
|
||||||
|
chunks: [],
|
||||||
|
depNames: [],
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const structure = initialStructure;
|
||||||
|
|
||||||
|
const finalStructure: ICodeStruct = await this.plugins.reduce(
|
||||||
|
async (previousPluginOperation: Promise<ICodeStruct>, plugin) => {
|
||||||
|
const modifiedStructure = await previousPluginOperation;
|
||||||
|
return plugin(modifiedStructure);
|
||||||
|
},
|
||||||
|
Promise.resolve(structure),
|
||||||
|
);
|
||||||
|
|
||||||
|
const chunks = groupChunks(finalStructure.chunks);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chunks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPlugins() {
|
||||||
|
return this.plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addPlugin(plugin: BuilderComponentPlugin) {
|
||||||
|
this.plugins.push(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
packages/code-generator/src/generator/CodeBuilder.ts
Normal file
100
packages/code-generator/src/generator/CodeBuilder.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import {
|
||||||
|
ChunkContent,
|
||||||
|
ChunkType,
|
||||||
|
CodeGeneratorError,
|
||||||
|
CodeGeneratorFunction,
|
||||||
|
ICodeBuilder,
|
||||||
|
ICodeChunk,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
export default class Builder implements ICodeBuilder {
|
||||||
|
private chunkDefinitions: ICodeChunk[] = [];
|
||||||
|
|
||||||
|
private generators: { [key: string]: CodeGeneratorFunction<ChunkContent> } = {
|
||||||
|
[ChunkType.STRING]: (str: string) => str, // no-op for string chunks
|
||||||
|
[ChunkType.JSON]: (json: object) => JSON.stringify(json), // stringify json to string
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(chunkDefinitions: ICodeChunk[] = []) {
|
||||||
|
this.chunkDefinitions = chunkDefinitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links all chunks together based on their requirements. Returns an array
|
||||||
|
* of ordered chunk names which need to be compiled and glued together.
|
||||||
|
*/
|
||||||
|
public link(chunkDefinitions: ICodeChunk[] = []): string {
|
||||||
|
const chunks = chunkDefinitions || this.chunkDefinitions;
|
||||||
|
if (chunks.length <= 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const unprocessedChunks = chunks.map(chunk => {
|
||||||
|
return {
|
||||||
|
name: chunk.name,
|
||||||
|
type: chunk.type,
|
||||||
|
content: chunk.content,
|
||||||
|
linkAfter: this.cleanupInvalidChunks(chunk.linkAfter, chunks),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultingString: string[] = [];
|
||||||
|
|
||||||
|
while (unprocessedChunks.length > 0) {
|
||||||
|
let indexToRemove = 0;
|
||||||
|
for (let index = 0; index < unprocessedChunks.length; index++) {
|
||||||
|
if (unprocessedChunks[index].linkAfter.length <= 0) {
|
||||||
|
indexToRemove = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unprocessedChunks[indexToRemove].linkAfter.length > 0) {
|
||||||
|
throw new CodeGeneratorError(
|
||||||
|
'Operation aborted. Reason: cyclic dependency between chunks.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type, content, name } = unprocessedChunks[indexToRemove];
|
||||||
|
const compiledContent = this.generateByType(type, content);
|
||||||
|
if (compiledContent) {
|
||||||
|
resultingString.push(compiledContent + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
unprocessedChunks.splice(indexToRemove, 1);
|
||||||
|
unprocessedChunks.forEach(
|
||||||
|
// remove the processed chunk from all the linkAfter arrays from the remaining chunks
|
||||||
|
ch => (ch.linkAfter = ch.linkAfter.filter(after => after !== name)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultingString.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateByType(type: string, content: unknown): string {
|
||||||
|
if (!content) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (Array.isArray(content)) {
|
||||||
|
return content
|
||||||
|
.map(contentItem => this.generateByType(type, contentItem))
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.generators[type]) {
|
||||||
|
throw new Error(
|
||||||
|
`Attempted to generate unknown type ${type}. Please register a generator for this type in builder/index.ts`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.generators[type](content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove invalid chunks (which did not end up being created) from the linkAfter fields
|
||||||
|
// one use-case is when you want to remove the import plugin
|
||||||
|
private cleanupInvalidChunks(linkAfter: string[], chunks: ICodeChunk[]) {
|
||||||
|
return linkAfter.filter(chunkName =>
|
||||||
|
chunks.some(chunk => chunk.name === chunkName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
packages/code-generator/src/generator/ModuleBuilder.ts
Normal file
79
packages/code-generator/src/generator/ModuleBuilder.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
CodeGeneratorError,
|
||||||
|
ICodeChunk,
|
||||||
|
ICompiledModule,
|
||||||
|
IModuleBuilder,
|
||||||
|
IResultFile,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
import { COMMON_SUB_MODULE_NAME } from '../const/generator';
|
||||||
|
|
||||||
|
import ChunkBuilder from './ChunkBuilder';
|
||||||
|
import CodeBuilder from './CodeBuilder';
|
||||||
|
|
||||||
|
import ResultFile from '../model/ResultFile';
|
||||||
|
|
||||||
|
export function createModuleBuilder(
|
||||||
|
options: {
|
||||||
|
plugins: BuilderComponentPlugin[];
|
||||||
|
mainFileName?: string;
|
||||||
|
} = {
|
||||||
|
plugins: [],
|
||||||
|
},
|
||||||
|
): IModuleBuilder {
|
||||||
|
const chunkGenerator = new ChunkBuilder(options.plugins);
|
||||||
|
const linker = new CodeBuilder();
|
||||||
|
|
||||||
|
const generateModule = async (input: unknown): Promise<ICompiledModule> => {
|
||||||
|
const moduleMainName = options.mainFileName || COMMON_SUB_MODULE_NAME;
|
||||||
|
if (chunkGenerator.getPlugins().length <= 0) {
|
||||||
|
throw new CodeGeneratorError(
|
||||||
|
'No plugins found. Component generation cannot work without any plugins!',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files: IResultFile[] = [];
|
||||||
|
|
||||||
|
const { chunks } = await chunkGenerator.run(input);
|
||||||
|
chunks.forEach(fileChunkList => {
|
||||||
|
const content = linker.link(fileChunkList);
|
||||||
|
const file = new ResultFile(
|
||||||
|
fileChunkList[0].subModule || moduleMainName,
|
||||||
|
fileChunkList[0].fileType,
|
||||||
|
content,
|
||||||
|
);
|
||||||
|
files.push(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkCodeChunks = (
|
||||||
|
chunks: Record<string, ICodeChunk[]>,
|
||||||
|
fileName: string,
|
||||||
|
) => {
|
||||||
|
const files: IResultFile[] = [];
|
||||||
|
|
||||||
|
Object.keys(chunks).forEach(fileKey => {
|
||||||
|
const fileChunkList = chunks[fileKey];
|
||||||
|
const content = linker.link(fileChunkList);
|
||||||
|
const file = new ResultFile(
|
||||||
|
fileChunkList[0].subModule || fileName,
|
||||||
|
fileChunkList[0].fileType,
|
||||||
|
content,
|
||||||
|
);
|
||||||
|
files.push(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
generateModule,
|
||||||
|
linkCodeChunks,
|
||||||
|
addPlugin: chunkGenerator.addPlugin.bind(chunkGenerator),
|
||||||
|
};
|
||||||
|
}
|
||||||
272
packages/code-generator/src/generator/ProjectBuilder.ts
Normal file
272
packages/code-generator/src/generator/ProjectBuilder.ts
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
import {
|
||||||
|
IModuleBuilder,
|
||||||
|
IParseResult,
|
||||||
|
IProjectBuilder,
|
||||||
|
IProjectPlugins,
|
||||||
|
IProjectSchema,
|
||||||
|
IProjectTemplate,
|
||||||
|
IResultDir,
|
||||||
|
IResultFile,
|
||||||
|
ISchemaParser,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
import ResultDir from '@/model/ResultDir';
|
||||||
|
import SchemaParser from '@/parse/SchemaParser';
|
||||||
|
|
||||||
|
import { createModuleBuilder } from '@/generator/ModuleBuilder';
|
||||||
|
|
||||||
|
interface IModuleInfo {
|
||||||
|
moduleName?: string;
|
||||||
|
path: string[];
|
||||||
|
files: IResultFile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDirFromRoot(root: IResultDir, path: string[]): IResultDir {
|
||||||
|
let current: IResultDir = root;
|
||||||
|
path.forEach(p => {
|
||||||
|
const exist = current.dirs.find(d => d.name === p);
|
||||||
|
if (exist) {
|
||||||
|
current = exist;
|
||||||
|
} else {
|
||||||
|
const newDir = new ResultDir(p);
|
||||||
|
current.addDirectory(newDir);
|
||||||
|
current = newDir;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProjectBuilder implements IProjectBuilder {
|
||||||
|
private template: IProjectTemplate;
|
||||||
|
private plugins: IProjectPlugins;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
template,
|
||||||
|
plugins,
|
||||||
|
}: {
|
||||||
|
template: IProjectTemplate;
|
||||||
|
plugins: IProjectPlugins;
|
||||||
|
}) {
|
||||||
|
this.template = template;
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async generateProject(schema: IProjectSchema): Promise<IResultDir> {
|
||||||
|
// Init working parts
|
||||||
|
const schemaParser: ISchemaParser = new SchemaParser();
|
||||||
|
const builders = this.createModuleBuilders();
|
||||||
|
const projectRoot = this.template.generateTemplate();
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
// Parse / Format
|
||||||
|
|
||||||
|
// Preprocess
|
||||||
|
// Colllect Deps
|
||||||
|
// Parse JSExpression
|
||||||
|
const parseResult: IParseResult = schemaParser.parse(schema);
|
||||||
|
let buildResult: IModuleInfo[] = [];
|
||||||
|
|
||||||
|
// Generator Code module
|
||||||
|
// components
|
||||||
|
// pages
|
||||||
|
const containerBuildResult: IModuleInfo[] = await Promise.all<IModuleInfo>(
|
||||||
|
parseResult.containers.map(async containerInfo => {
|
||||||
|
let builder: IModuleBuilder;
|
||||||
|
let path: string[];
|
||||||
|
if (containerInfo.containerType === 'Page') {
|
||||||
|
builder = builders.pages;
|
||||||
|
path = this.template.slots.pages.path;
|
||||||
|
} else {
|
||||||
|
builder = builders.components;
|
||||||
|
path = this.template.slots.components.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { files } = await builder.generateModule(containerInfo);
|
||||||
|
|
||||||
|
return {
|
||||||
|
moduleName: containerInfo.fileName,
|
||||||
|
path,
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
buildResult = buildResult.concat(containerBuildResult);
|
||||||
|
|
||||||
|
// router
|
||||||
|
if (parseResult.globalRouter && builders.router) {
|
||||||
|
const { files } = await builders.router.generateModule(
|
||||||
|
parseResult.globalRouter,
|
||||||
|
);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.router.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// entry
|
||||||
|
if (parseResult.project && builders.entry) {
|
||||||
|
const { files } = await builders.entry.generateModule(
|
||||||
|
parseResult.project,
|
||||||
|
);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.entry.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// constants?
|
||||||
|
if (
|
||||||
|
parseResult.project &&
|
||||||
|
builders.constants &&
|
||||||
|
this.template.slots.constants
|
||||||
|
) {
|
||||||
|
const { files } = await builders.constants.generateModule(
|
||||||
|
parseResult.project,
|
||||||
|
);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.constants.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// utils?
|
||||||
|
if (
|
||||||
|
parseResult.globalUtils &&
|
||||||
|
builders.utils &&
|
||||||
|
this.template.slots.utils
|
||||||
|
) {
|
||||||
|
const { files } = await builders.utils.generateModule(
|
||||||
|
parseResult.globalUtils,
|
||||||
|
);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.utils.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// i18n?
|
||||||
|
if (parseResult.globalI18n && builders.i18n && this.template.slots.i18n) {
|
||||||
|
const { files } = await builders.i18n.generateModule(
|
||||||
|
parseResult.globalI18n,
|
||||||
|
);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.i18n.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// globalStyle
|
||||||
|
if (parseResult.project && builders.globalStyle) {
|
||||||
|
const { files } = await builders.globalStyle.generateModule(
|
||||||
|
parseResult.project,
|
||||||
|
);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.globalStyle.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// htmlEntry
|
||||||
|
if (parseResult.project && builders.htmlEntry) {
|
||||||
|
const { files } = await builders.htmlEntry.generateModule(
|
||||||
|
parseResult.project,
|
||||||
|
);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.htmlEntry.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// packageJSON
|
||||||
|
if (parseResult.project && builders.packageJSON) {
|
||||||
|
const { files } = await builders.packageJSON.generateModule(
|
||||||
|
parseResult.project,
|
||||||
|
);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.packageJSON.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post Process
|
||||||
|
|
||||||
|
// Combine Modules
|
||||||
|
buildResult.forEach(moduleInfo => {
|
||||||
|
let targetDir = getDirFromRoot(projectRoot, moduleInfo.path);
|
||||||
|
if (moduleInfo.moduleName) {
|
||||||
|
const dir = new ResultDir(moduleInfo.moduleName);
|
||||||
|
targetDir.addDirectory(dir);
|
||||||
|
targetDir = dir;
|
||||||
|
}
|
||||||
|
moduleInfo.files.forEach(file => targetDir.addFile(file));
|
||||||
|
});
|
||||||
|
|
||||||
|
return projectRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createModuleBuilders(): Record<string, IModuleBuilder> {
|
||||||
|
const builders: Record<string, IModuleBuilder> = {};
|
||||||
|
|
||||||
|
builders.components = createModuleBuilder({
|
||||||
|
plugins: this.plugins.components,
|
||||||
|
});
|
||||||
|
builders.pages = createModuleBuilder({ plugins: this.plugins.pages });
|
||||||
|
builders.router = createModuleBuilder({
|
||||||
|
plugins: this.plugins.router,
|
||||||
|
mainFileName: this.template.slots.router.fileName,
|
||||||
|
});
|
||||||
|
builders.entry = createModuleBuilder({
|
||||||
|
plugins: this.plugins.entry,
|
||||||
|
mainFileName: this.template.slots.entry.fileName,
|
||||||
|
});
|
||||||
|
builders.globalStyle = createModuleBuilder({
|
||||||
|
plugins: this.plugins.globalStyle,
|
||||||
|
mainFileName: this.template.slots.globalStyle.fileName,
|
||||||
|
});
|
||||||
|
builders.htmlEntry = createModuleBuilder({
|
||||||
|
plugins: this.plugins.htmlEntry,
|
||||||
|
mainFileName: this.template.slots.htmlEntry.fileName,
|
||||||
|
});
|
||||||
|
builders.packageJSON = createModuleBuilder({
|
||||||
|
plugins: this.plugins.packageJSON,
|
||||||
|
mainFileName: this.template.slots.packageJSON.fileName,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.template.slots.constants && this.plugins.constants) {
|
||||||
|
builders.constants = createModuleBuilder({
|
||||||
|
plugins: this.plugins.constants,
|
||||||
|
mainFileName: this.template.slots.constants.fileName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.template.slots.utils && this.plugins.utils) {
|
||||||
|
builders.utils = createModuleBuilder({
|
||||||
|
plugins: this.plugins.utils,
|
||||||
|
mainFileName: this.template.slots.utils.fileName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.template.slots.i18n && this.plugins.i18n) {
|
||||||
|
builders.i18n = createModuleBuilder({
|
||||||
|
plugins: this.plugins.i18n,
|
||||||
|
mainFileName: this.template.slots.i18n.fileName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return builders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createProjectBuilder({
|
||||||
|
template,
|
||||||
|
plugins,
|
||||||
|
}: {
|
||||||
|
template: IProjectTemplate;
|
||||||
|
plugins: IProjectPlugins;
|
||||||
|
}): IProjectBuilder {
|
||||||
|
return new ProjectBuilder({
|
||||||
|
template,
|
||||||
|
plugins,
|
||||||
|
});
|
||||||
|
}
|
||||||
15
packages/code-generator/src/index.ts
Normal file
15
packages/code-generator/src/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* 低代码引擎的出码模块,负责将编排产出的 Schema 转换成实际可执行的代码。
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { createProjectBuilder } from '@/generator/ProjectBuilder';
|
||||||
|
import createIceJsProjectBuilder from '@/solutions/icejs';
|
||||||
|
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
createProjectBuilder,
|
||||||
|
solutions: {
|
||||||
|
icejs: createIceJsProjectBuilder,
|
||||||
|
},
|
||||||
|
};
|
||||||
31
packages/code-generator/src/model/ResultDir.ts
Normal file
31
packages/code-generator/src/model/ResultDir.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { CodeGeneratorError, IResultDir, IResultFile } from '../types';
|
||||||
|
|
||||||
|
export default class ResultDir implements IResultDir {
|
||||||
|
public name: string;
|
||||||
|
public dirs: IResultDir[];
|
||||||
|
public files: IResultFile[];
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
this.dirs = [];
|
||||||
|
this.files = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public addDirectory(dir: IResultDir): void {
|
||||||
|
if (this.dirs.findIndex(d => d.name === dir.name) < 0) {
|
||||||
|
this.dirs.push(dir);
|
||||||
|
} else {
|
||||||
|
throw new CodeGeneratorError('Adding same directory to one directory');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addFile(file: IResultFile): void {
|
||||||
|
if (
|
||||||
|
this.files.findIndex(f => f.name === file.name && f.ext === file.ext) < 0
|
||||||
|
) {
|
||||||
|
this.files.push(file);
|
||||||
|
} else {
|
||||||
|
throw new CodeGeneratorError('Adding same file to one directory');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
packages/code-generator/src/model/ResultFile.ts
Normal file
13
packages/code-generator/src/model/ResultFile.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { IResultFile } from '../types';
|
||||||
|
|
||||||
|
export default class ResultFile implements IResultFile {
|
||||||
|
public name: string;
|
||||||
|
public ext: string;
|
||||||
|
public content: string;
|
||||||
|
|
||||||
|
constructor(name: string, ext: string = 'jsx', content: string = '') {
|
||||||
|
this.name = name;
|
||||||
|
this.ext = ext;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
185
packages/code-generator/src/parse/SchemaParser.ts
Normal file
185
packages/code-generator/src/parse/SchemaParser.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* 解析器是对输入的固定格式数据做拆解,使其符合引擎后续步骤预期,完成统一处理逻辑的步骤。
|
||||||
|
* 本解析器面向的是标准 schema 协议。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SUPPORT_SCHEMA_VERSION_LIST } from '../const';
|
||||||
|
|
||||||
|
import { handleChildren } from '../utils/children';
|
||||||
|
import { uniqueArray } from '../utils/common';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChildNodeItem,
|
||||||
|
ChildNodeType,
|
||||||
|
CodeGeneratorError,
|
||||||
|
CompatibilityError,
|
||||||
|
DependencyType,
|
||||||
|
IBasicSchema,
|
||||||
|
IComponentNodeItem,
|
||||||
|
IContainerInfo,
|
||||||
|
IContainerNodeItem,
|
||||||
|
IExternalDependency,
|
||||||
|
IInternalDependency,
|
||||||
|
InternalDependencyType,
|
||||||
|
IPageMeta,
|
||||||
|
IParseResult,
|
||||||
|
IProjectSchema,
|
||||||
|
ISchemaParser,
|
||||||
|
IUtilItem,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
const defaultContainer: IContainerInfo = {
|
||||||
|
containerType: 'Component',
|
||||||
|
componentName: 'Index',
|
||||||
|
fileName: 'Index',
|
||||||
|
css: '',
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
class SchemaParser implements ISchemaParser {
|
||||||
|
public validate(schema: IBasicSchema): boolean {
|
||||||
|
if (SUPPORT_SCHEMA_VERSION_LIST.indexOf(schema.version) < 0) {
|
||||||
|
throw new CompatibilityError(
|
||||||
|
`Not support schema with version [${schema.version}]`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parse(schema: IProjectSchema): IParseResult {
|
||||||
|
// TODO: collect utils depends in JSExpression
|
||||||
|
const compDeps: Record<string, IExternalDependency> = {};
|
||||||
|
const internalDeps: Record<string, IInternalDependency> = {};
|
||||||
|
let utilsDeps: IExternalDependency[] = [];
|
||||||
|
|
||||||
|
// 解析三方组件依赖
|
||||||
|
schema.componentsMap.forEach(info => {
|
||||||
|
info.dependencyType = DependencyType.External;
|
||||||
|
info.importName = info.componentName;
|
||||||
|
compDeps[info.componentName] = info;
|
||||||
|
});
|
||||||
|
|
||||||
|
let containers: IContainerInfo[];
|
||||||
|
// Test if this is a lowcode component without container
|
||||||
|
if (schema.componentsTree.length > 0) {
|
||||||
|
const firstRoot: IContainerNodeItem = schema
|
||||||
|
.componentsTree[0] as IContainerNodeItem;
|
||||||
|
|
||||||
|
if (!firstRoot.fileName) {
|
||||||
|
// 整个 schema 描述一个容器,且无根节点定义
|
||||||
|
const container: IContainerInfo = {
|
||||||
|
...defaultContainer,
|
||||||
|
children: schema.componentsTree as IComponentNodeItem[],
|
||||||
|
};
|
||||||
|
containers = [container];
|
||||||
|
} else {
|
||||||
|
// 普通带 1 到多个容器的 schema
|
||||||
|
containers = schema.componentsTree.map(n => {
|
||||||
|
const subRoot = n as IContainerNodeItem;
|
||||||
|
const container: IContainerInfo = {
|
||||||
|
...subRoot,
|
||||||
|
containerType: subRoot.componentName,
|
||||||
|
componentName: subRoot.fileName,
|
||||||
|
};
|
||||||
|
return container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new CodeGeneratorError(`Can't find anything to generator.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立所有容器的内部依赖索引
|
||||||
|
containers.forEach(container => {
|
||||||
|
let type;
|
||||||
|
switch (container.containerType) {
|
||||||
|
case 'Page':
|
||||||
|
type = InternalDependencyType.PAGE;
|
||||||
|
break;
|
||||||
|
case 'Block':
|
||||||
|
type = InternalDependencyType.BLOCK;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = InternalDependencyType.COMPONENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dep: IInternalDependency = {
|
||||||
|
type,
|
||||||
|
moduleName: container.componentName,
|
||||||
|
destructuring: false,
|
||||||
|
exportName: container.componentName,
|
||||||
|
dependencyType: DependencyType.Internal,
|
||||||
|
};
|
||||||
|
|
||||||
|
internalDeps[dep.moduleName] = dep;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分析容器内部组件依赖
|
||||||
|
containers.forEach(container => {
|
||||||
|
if (container.children) {
|
||||||
|
// const depNames = this.getComponentNames(container.children);
|
||||||
|
// container.deps = uniqueArray<string>(depNames)
|
||||||
|
// .map(depName => internalDeps[depName] || compDeps[depName])
|
||||||
|
// .filter(dep => !!dep);
|
||||||
|
container.deps = Object.keys(compDeps).map(
|
||||||
|
depName => compDeps[depName],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分析路由配置
|
||||||
|
const routes = containers
|
||||||
|
.filter(container => container.containerType === 'Page')
|
||||||
|
.map(page => {
|
||||||
|
const meta = page.meta as IPageMeta;
|
||||||
|
return {
|
||||||
|
path: meta.router,
|
||||||
|
componentName: page.componentName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const routerDeps = routes
|
||||||
|
.map(r => internalDeps[r.componentName] || compDeps[r.componentName])
|
||||||
|
.filter(dep => !!dep);
|
||||||
|
|
||||||
|
// 分析 Utils 依赖
|
||||||
|
let utils: IUtilItem[];
|
||||||
|
if (schema.utils) {
|
||||||
|
utils = schema.utils;
|
||||||
|
utilsDeps = schema.utils
|
||||||
|
.filter(u => u.type !== 'function')
|
||||||
|
.map(u => u.content as IExternalDependency);
|
||||||
|
} else {
|
||||||
|
utils = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
containers,
|
||||||
|
globalUtils: {
|
||||||
|
utils,
|
||||||
|
deps: utilsDeps,
|
||||||
|
},
|
||||||
|
globalI18n: schema.i18n,
|
||||||
|
globalRouter: {
|
||||||
|
routes,
|
||||||
|
deps: routerDeps,
|
||||||
|
},
|
||||||
|
project: {
|
||||||
|
meta: schema.meta,
|
||||||
|
config: schema.config,
|
||||||
|
css: schema.css,
|
||||||
|
constants: schema.constants,
|
||||||
|
i18n: schema.i18n,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getComponentNames(children: ChildNodeType): string[] {
|
||||||
|
return handleChildren<string>(children, {
|
||||||
|
node: (i: IComponentNodeItem) => [i.componentName],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SchemaParser;
|
||||||
146
packages/code-generator/src/plugins/common/esmodule.ts
Normal file
146
packages/code-generator/src/plugins/common/esmodule.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
CodeGeneratorError,
|
||||||
|
DependencyType,
|
||||||
|
FileType,
|
||||||
|
ICodeChunk,
|
||||||
|
ICodeStruct,
|
||||||
|
IDependency,
|
||||||
|
IExternalDependency,
|
||||||
|
IInternalDependency,
|
||||||
|
IWithDependency,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
|
function groupDepsByPack(deps: IDependency[]): Record<string, IDependency[]> {
|
||||||
|
const depMap: Record<string, IDependency[]> = {};
|
||||||
|
|
||||||
|
const addDep = (pkg: string, dep: IDependency) => {
|
||||||
|
if (!depMap[pkg]) {
|
||||||
|
depMap[pkg] = [];
|
||||||
|
}
|
||||||
|
depMap[pkg].push(dep);
|
||||||
|
};
|
||||||
|
|
||||||
|
deps.forEach(dep => {
|
||||||
|
if (dep.dependencyType === DependencyType.Internal) {
|
||||||
|
addDep(
|
||||||
|
`${(dep as IInternalDependency).moduleName}${dep.main || ''}`,
|
||||||
|
dep,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addDep(`${(dep as IExternalDependency).package}${dep.main || ''}`, dep);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return depMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPackageImport(
|
||||||
|
pkg: string,
|
||||||
|
deps: IDependency[],
|
||||||
|
isJSX: boolean,
|
||||||
|
): ICodeChunk[] {
|
||||||
|
const chunks: ICodeChunk[] = [];
|
||||||
|
let defaultImport: string = '';
|
||||||
|
let defaultImportAs: string = '';
|
||||||
|
const imports: Record<string, string> = {};
|
||||||
|
|
||||||
|
deps.forEach(dep => {
|
||||||
|
const srcName = dep.exportName;
|
||||||
|
let targetName = dep.importName || dep.exportName;
|
||||||
|
if (dep.subName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dep.subName) {
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: isJSX ? FileType.JSX : FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
content: `const ${targetName} = ${srcName}.${dep.subName};`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
targetName = srcName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dep.destructuring) {
|
||||||
|
imports[srcName] = targetName;
|
||||||
|
} else if (defaultImport) {
|
||||||
|
throw new CodeGeneratorError(
|
||||||
|
`[${pkg}] has more than one default export.`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
defaultImport = srcName;
|
||||||
|
defaultImportAs = targetName;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = Object.keys(imports).map(src =>
|
||||||
|
src === imports[src] ? src : `${src} as ${imports[src]}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const statementL = ['import'];
|
||||||
|
if (defaultImport) {
|
||||||
|
statementL.push(defaultImportAs);
|
||||||
|
if (items.length > 0) {
|
||||||
|
statementL.push(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (items.length > 0) {
|
||||||
|
statementL.push(`{ ${items.join(', ')} }`);
|
||||||
|
}
|
||||||
|
statementL.push('from');
|
||||||
|
|
||||||
|
if (deps[0].dependencyType === DependencyType.Internal) {
|
||||||
|
// TODO: Internal Deps path use project slot setting
|
||||||
|
statementL.push(`'@/${(deps[0] as IInternalDependency).type}/${pkg}';`);
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: isJSX ? FileType.JSX : FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: statementL.join(' '),
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
statementL.push(`'${pkg}';`);
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: isJSX ? FileType.JSX : FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: statementL.join(' '),
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isJSX = next.chunks.some(chunk => chunk.fileType === FileType.JSX);
|
||||||
|
|
||||||
|
const ir = next.ir as IWithDependency;
|
||||||
|
|
||||||
|
if (ir && ir.deps && ir.deps.length > 0) {
|
||||||
|
const packs = groupDepsByPack(ir.deps);
|
||||||
|
|
||||||
|
Object.keys(packs).forEach(pkg => {
|
||||||
|
const chunks = buildPackageImport(pkg, packs[pkg], isJSX);
|
||||||
|
next.chunks.push.apply(next.chunks, chunks);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
27
packages/code-generator/src/plugins/common/requireUtils.ts
Normal file
27
packages/code-generator/src/plugins/common/requireUtils.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: `import * from 'react';`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
16
packages/code-generator/src/plugins/component/react/const.ts
Normal file
16
packages/code-generator/src/plugins/component/react/const.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export const REACT_CHUNK_NAME = {
|
||||||
|
ClassStart: 'ReactComponentClassDefineStart',
|
||||||
|
ClassEnd: 'ReactComponentClassDefineEnd',
|
||||||
|
ClassLifeCycle: 'ReactComponentClassMemberLifeCycle',
|
||||||
|
ClassMethod: 'ReactComponentClassMemberMethod',
|
||||||
|
ClassRenderStart: 'ReactComponentClassRenderStart',
|
||||||
|
ClassRenderPre: 'ReactComponentClassRenderPre',
|
||||||
|
ClassRenderEnd: 'ReactComponentClassRenderEnd',
|
||||||
|
ClassRenderJSX: 'ReactComponentClassRenderJSX',
|
||||||
|
ClassConstructorStart: 'ReactComponentClassConstructorStart',
|
||||||
|
ClassConstructorEnd: 'ReactComponentClassConstructorEnd',
|
||||||
|
ClassConstructorContent: 'ReactComponentClassConstructorContent',
|
||||||
|
ClassDidMountStart: 'ReactComponentClassDidMountStart',
|
||||||
|
ClassDidMountEnd: 'ReactComponentClassDidMountEnd',
|
||||||
|
ClassDidMountContent: 'ReactComponentClassDidMountContent',
|
||||||
|
};
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassStart,
|
||||||
|
content: `class ${ir.componentName} extends React.Component {`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassEnd,
|
||||||
|
content: `}`,
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassStart, REACT_CHUNK_NAME.ClassRenderEnd],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassConstructorStart,
|
||||||
|
content: 'constructor(props, context) { super(props); ',
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassStart],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassConstructorEnd,
|
||||||
|
content: '}',
|
||||||
|
linkAfter: [
|
||||||
|
REACT_CHUNK_NAME.ClassConstructorStart,
|
||||||
|
REACT_CHUNK_NAME.ClassConstructorContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassRenderStart,
|
||||||
|
content: 'render() {',
|
||||||
|
linkAfter: [
|
||||||
|
REACT_CHUNK_NAME.ClassStart,
|
||||||
|
REACT_CHUNK_NAME.ClassConstructorEnd,
|
||||||
|
REACT_CHUNK_NAME.ClassLifeCycle,
|
||||||
|
REACT_CHUNK_NAME.ClassMethod,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassRenderEnd,
|
||||||
|
content: '}',
|
||||||
|
linkAfter: [
|
||||||
|
REACT_CHUNK_NAME.ClassRenderStart,
|
||||||
|
REACT_CHUNK_NAME.ClassRenderPre,
|
||||||
|
REACT_CHUNK_NAME.ClassRenderJSX,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `export default ${ir.componentName};`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
REACT_CHUNK_NAME.ClassEnd,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import { generateCompositeType } from '../../utils/compositeType';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
if (ir.state) {
|
||||||
|
const state = ir.state;
|
||||||
|
const fields = Object.keys(state).map<string>(stateName => {
|
||||||
|
const [isString, value] = generateCompositeType(state[stateName]);
|
||||||
|
return `${stateName}: ${isString ? `'${value}'` : value},`;
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassConstructorContent,
|
||||||
|
content: `this.state = { ${fields.join('')} };`,
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import { generateCompositeType } from '../../utils/compositeType';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
if (ir.state) {
|
||||||
|
const state = ir.state;
|
||||||
|
const fields = Object.keys(state).map<string>(stateName => {
|
||||||
|
const [isString, value] = generateCompositeType(state[stateName]);
|
||||||
|
return `${stateName}: ${isString ? `'${value}'` : value},`;
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassConstructorContent,
|
||||||
|
content: `this.state = { ${fields.join('')} };`,
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassConstructorContent,
|
||||||
|
content: `this.utils = utils;`,
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getFuncExprBody,
|
||||||
|
transformFuncExpr2MethodMember,
|
||||||
|
} from '../../utils/jsExpression';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
CodeGeneratorError,
|
||||||
|
FileType,
|
||||||
|
ICodeChunk,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
IJSExpression,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
if (ir.lifeCycles) {
|
||||||
|
const lifeCycles = ir.lifeCycles;
|
||||||
|
const chunks = Object.keys(lifeCycles).map<ICodeChunk>(lifeCycleName => {
|
||||||
|
if (lifeCycleName === 'constructor') {
|
||||||
|
return {
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassConstructorContent,
|
||||||
|
content: getFuncExprBody(
|
||||||
|
(lifeCycles[lifeCycleName] as IJSExpression).value,
|
||||||
|
),
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassConstructorStart],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (lifeCycleName === 'render') {
|
||||||
|
return {
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassRenderPre,
|
||||||
|
content: getFuncExprBody(
|
||||||
|
(lifeCycles[lifeCycleName] as IJSExpression).value,
|
||||||
|
),
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassRenderStart],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
lifeCycleName === 'componentDidMount' ||
|
||||||
|
lifeCycleName === 'componentDidUpdate' ||
|
||||||
|
lifeCycleName === 'componentWillUnmount' ||
|
||||||
|
lifeCycleName === 'componentDidCatch'
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassLifeCycle,
|
||||||
|
content: transformFuncExpr2MethodMember(
|
||||||
|
lifeCycleName,
|
||||||
|
(lifeCycles[lifeCycleName] as IJSExpression).value,
|
||||||
|
),
|
||||||
|
linkAfter: [
|
||||||
|
REACT_CHUNK_NAME.ClassStart,
|
||||||
|
REACT_CHUNK_NAME.ClassConstructorEnd,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CodeGeneratorError('Unknown life cycle method name');
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push.apply(next.chunks, chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import { transformFuncExpr2MethodMember } from '../../utils/jsExpression';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeChunk,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
IJSExpression,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
if (ir.methods) {
|
||||||
|
const methods = ir.methods;
|
||||||
|
const chunks = Object.keys(methods).map<ICodeChunk>(methodName => ({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassMethod,
|
||||||
|
content: transformFuncExpr2MethodMember(
|
||||||
|
methodName,
|
||||||
|
(methods[methodName] as IJSExpression).value,
|
||||||
|
),
|
||||||
|
linkAfter: [
|
||||||
|
REACT_CHUNK_NAME.ClassStart,
|
||||||
|
REACT_CHUNK_NAME.ClassConstructorEnd,
|
||||||
|
REACT_CHUNK_NAME.ClassLifeCycle,
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
next.chunks.push.apply(next.chunks, chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
149
packages/code-generator/src/plugins/component/react/jsx.ts
Normal file
149
packages/code-generator/src/plugins/component/react/jsx.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChildNodeItem,
|
||||||
|
ChildNodeType,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IComponentNodeItem,
|
||||||
|
IContainerInfo,
|
||||||
|
IInlineStyle,
|
||||||
|
IJSExpression,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
import { handleChildren } from '@/utils/children';
|
||||||
|
import { generateCompositeType } from '../../utils/compositeType';
|
||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
function generateInlineStyle(style: IInlineStyle): string | null {
|
||||||
|
const attrLines = Object.keys(style).map((cssAttribute: string) => {
|
||||||
|
const [isString, valueStr] = generateCompositeType(style[cssAttribute]);
|
||||||
|
const valuePart = isString ? `'${valueStr}'` : valueStr;
|
||||||
|
return `${cssAttribute}: ${valuePart},`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (attrLines.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `{ ${attrLines.join('')} }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateAttr(attrName: string, attrValue: any): string {
|
||||||
|
if (attrName === 'initValue' || attrName === 'labelCol') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const [isString, valueStr] = generateCompositeType(attrValue);
|
||||||
|
return `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapNodeName(src: string): string {
|
||||||
|
if (src === 'Div') {
|
||||||
|
return 'div';
|
||||||
|
}
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNode(nodeItem: IComponentNodeItem): string {
|
||||||
|
const codePieces: string[] = [];
|
||||||
|
let propLines: string[] = [];
|
||||||
|
const { className, style, ...props } = nodeItem.props;
|
||||||
|
|
||||||
|
codePieces.push(`<${mapNodeName(nodeItem.componentName)}`);
|
||||||
|
if (className) {
|
||||||
|
propLines.push(`className="${className}"`);
|
||||||
|
}
|
||||||
|
if (style) {
|
||||||
|
const inlineStyle = generateInlineStyle(style);
|
||||||
|
if (inlineStyle !== null) {
|
||||||
|
propLines.push(`style={${inlineStyle}}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
propLines = propLines.concat(
|
||||||
|
Object.keys(props).map((propName: string) =>
|
||||||
|
generateAttr(propName, props[propName]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
codePieces.push(` ${propLines.join(' ')} `);
|
||||||
|
|
||||||
|
if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) {
|
||||||
|
codePieces.push('>');
|
||||||
|
const childrenLines = generateChildren(nodeItem.children);
|
||||||
|
codePieces.push.apply(codePieces, childrenLines);
|
||||||
|
codePieces.push(`</${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 = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
let jsxContent: string;
|
||||||
|
if (!ir.children || (ir.children as unknown[]).length === 0) {
|
||||||
|
jsxContent = 'null';
|
||||||
|
} else {
|
||||||
|
const childrenCode = generateChildren(ir.children);
|
||||||
|
if (childrenCode.length === 1) {
|
||||||
|
jsxContent = `(${childrenCode[0]})`;
|
||||||
|
} else {
|
||||||
|
jsxContent = `(<React.Fragment>${childrenCode.join(
|
||||||
|
'',
|
||||||
|
)}</React.Fragment>)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassRenderJSX,
|
||||||
|
content: `return ${jsxContent};`,
|
||||||
|
linkAfter: [
|
||||||
|
REACT_CHUNK_NAME.ClassRenderStart,
|
||||||
|
REACT_CHUNK_NAME.ClassRenderPre,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `import React from 'react';`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
37
packages/code-generator/src/plugins/component/style/css.ts
Normal file
37
packages/code-generator/src/plugins/component/style/css.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.CSS,
|
||||||
|
name: COMMON_CHUNK_NAME.StyleCssContent,
|
||||||
|
content: ir.css,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: `import './index.css';`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
54
packages/code-generator/src/plugins/project/constants.ts
Normal file
54
packages/code-generator/src/plugins/project/constants.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '@/const/generator';
|
||||||
|
import { generateCompositeType } from '@/plugins/utils/compositeType';
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
if (ir.constants) {
|
||||||
|
const [, constantStr] = generateCompositeType(ir.constants);
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
content: `
|
||||||
|
const constantConfig = ${constantStr};
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `
|
||||||
|
export default constantConfig;
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '@/const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `
|
||||||
|
import { createApp } from 'ice';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
content: `
|
||||||
|
const appConfig = {
|
||||||
|
app: {
|
||||||
|
rootId: '${ir.config.targetRootID}',
|
||||||
|
},
|
||||||
|
router: {
|
||||||
|
type: '${ir.config.historyMode}',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
createApp(appConfig);
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '@/const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.HTML,
|
||||||
|
name: COMMON_CHUNK_NAME.HtmlContent,
|
||||||
|
content: `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>${ir.meta.name}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="${ir.config.targetRootID}"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '@/const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.SCSS,
|
||||||
|
name: COMMON_CHUNK_NAME.StyleDepsImport,
|
||||||
|
content: `
|
||||||
|
// 引入默认全局样式
|
||||||
|
@import '@alifd/next/reset.scss';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.SCSS,
|
||||||
|
name: COMMON_CHUNK_NAME.StyleCssContent,
|
||||||
|
content: `
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.SCSS,
|
||||||
|
name: COMMON_CHUNK_NAME.StyleCssContent,
|
||||||
|
content: ir.css || '',
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '@/const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IPackageJSON,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
interface IIceJsPackageJSON extends IPackageJSON {
|
||||||
|
ideMode: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
iceworks: {
|
||||||
|
type: string;
|
||||||
|
adapter: string;
|
||||||
|
};
|
||||||
|
originTemplate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
|
||||||
|
const packageJson: IIceJsPackageJSON = {
|
||||||
|
name: '@alifd/scaffold-lite-js',
|
||||||
|
version: '0.1.5',
|
||||||
|
description: '轻量级模板,使用 JavaScript,仅包含基础的 Layout。',
|
||||||
|
dependencies: {
|
||||||
|
moment: '^2.24.0',
|
||||||
|
react: '^16.4.1',
|
||||||
|
'react-dom': '^16.4.1',
|
||||||
|
'@alifd/theme-design-pro': '^0.x',
|
||||||
|
},
|
||||||
|
devDependencies: {
|
||||||
|
'@ice/spec': '^1.0.0',
|
||||||
|
'build-plugin-fusion': '^0.1.0',
|
||||||
|
'build-plugin-moment-locales': '^0.1.0',
|
||||||
|
eslint: '^6.0.1',
|
||||||
|
'ice.js': '^1.0.0',
|
||||||
|
stylelint: '^13.2.0',
|
||||||
|
'@ali/build-plugin-ice-def': '^0.1.0',
|
||||||
|
},
|
||||||
|
scripts: {
|
||||||
|
start: 'icejs start',
|
||||||
|
build: 'icejs build',
|
||||||
|
lint: 'npm run eslint && npm run stylelint',
|
||||||
|
eslint: 'eslint --cache --ext .js,.jsx ./',
|
||||||
|
stylelint: 'stylelint ./**/*.scss',
|
||||||
|
},
|
||||||
|
ideMode: {
|
||||||
|
name: 'ice-react',
|
||||||
|
},
|
||||||
|
iceworks: {
|
||||||
|
type: 'react',
|
||||||
|
adapter: 'adapter-react-v3',
|
||||||
|
},
|
||||||
|
engines: {
|
||||||
|
node: '>=8.0.0',
|
||||||
|
},
|
||||||
|
repository: {
|
||||||
|
type: 'git',
|
||||||
|
url: 'http://gitlab.alibaba-inc.com/msd/leak-scan/tree/master',
|
||||||
|
},
|
||||||
|
private: true,
|
||||||
|
originTemplate: '@alifd/scaffold-lite-js',
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.JSON,
|
||||||
|
fileType: FileType.JSON,
|
||||||
|
name: COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
content: packageJson,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '@/const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IRouterInfo,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IRouterInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: `
|
||||||
|
import BasicLayout from '@/layouts/BasicLayout';
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
content: `
|
||||||
|
const routerConfig = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: BasicLayout,
|
||||||
|
children: [
|
||||||
|
${ir.routes
|
||||||
|
.map(
|
||||||
|
route => `
|
||||||
|
{
|
||||||
|
path: '${route.path}',
|
||||||
|
component: ${route.componentName},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join(',')}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `
|
||||||
|
export default routerConfig;
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'README',
|
||||||
|
'md',
|
||||||
|
`
|
||||||
|
## Scaffold Lite
|
||||||
|
|
||||||
|
> 轻量级模板,使用 JavaScript,仅包含基础的 Layout。
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# 安装依赖
|
||||||
|
$ npm install
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
$ npm start # visit http://localhost:3333
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
[More docs](https://ice.work/docs/guide/about).
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
\`\`\`md
|
||||||
|
├── build/ # 构建产物
|
||||||
|
├── mock/ # 本地模拟数据
|
||||||
|
│ ├── index.[j,t]s
|
||||||
|
├── public/
|
||||||
|
│ ├── index.html # 应用入口 HTML
|
||||||
|
│ └── favicon.png # Favicon
|
||||||
|
├── src/ # 源码路径
|
||||||
|
│ ├── components/ # 自定义业务组件
|
||||||
|
│ │ └── Guide/
|
||||||
|
│ │ ├── index.[j,t]sx
|
||||||
|
│ │ ├── index.module.scss
|
||||||
|
│ ├── layouts/ # 布局组件
|
||||||
|
│ │ └── BasicLayout/
|
||||||
|
│ │ ├── index.[j,t]sx
|
||||||
|
│ │ └── index.module.scss
|
||||||
|
│ ├── pages/ # 页面
|
||||||
|
│ │ └── Home/ # home 页面,约定路由转成小写
|
||||||
|
│ │ ├── components/ # 页面级自定义业务组件
|
||||||
|
│ │ ├── models.[j,t]sx # 页面级数据状态
|
||||||
|
│ │ ├── index.[j,t]sx # 页面入口
|
||||||
|
│ │ └── index.module.scss # 页面样式文件
|
||||||
|
│ ├── configs/ # [可选] 配置文件
|
||||||
|
│ │ └── menu.[j,t]s # [可选] 菜单配置
|
||||||
|
│ ├── models/ # [可选] 应用级数据状态
|
||||||
|
│ │ └── user.[j,t]s
|
||||||
|
│ ├── utils/ # [可选] 工具库
|
||||||
|
│ ├── global.scss # 全局样式
|
||||||
|
│ ├── routes.[j,t]s # 路由配置
|
||||||
|
│ └── app.[j,t]s[x] # 应用入口脚本
|
||||||
|
├── build.json # 工程配置
|
||||||
|
├── README.md
|
||||||
|
├── package.json
|
||||||
|
├── .editorconfig
|
||||||
|
├── .eslintignore
|
||||||
|
├── .eslintrc.[j,t]s
|
||||||
|
├── .gitignore
|
||||||
|
├── .stylelintignore
|
||||||
|
├── .stylelintrc.[j,t]s
|
||||||
|
├── .gitignore
|
||||||
|
└── [j,t]sconfig.json
|
||||||
|
\`\`\`
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'abc',
|
||||||
|
'json',
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"type": "ice-app",
|
||||||
|
"builder": "@ali/builder-ice-app"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'build',
|
||||||
|
'json',
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"entry": "src/app.js",
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"build-plugin-fusion",
|
||||||
|
{
|
||||||
|
"themePackage": "@alifd/theme-design-pro"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"build-plugin-moment-locales",
|
||||||
|
{
|
||||||
|
"locales": [
|
||||||
|
"zh-cn"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@ali/build-plugin-ice-def"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'.editorconfig',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'.eslintignore',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
# 忽略目录
|
||||||
|
build/
|
||||||
|
tests/
|
||||||
|
demo/
|
||||||
|
.ice/
|
||||||
|
|
||||||
|
# node 覆盖率文件
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# 忽略文件
|
||||||
|
**/*-min.js
|
||||||
|
**/*.min.js
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'.eslintrc',
|
||||||
|
'js',
|
||||||
|
`
|
||||||
|
const { eslint } = require('@ice/spec');
|
||||||
|
|
||||||
|
module.exports = eslint;
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'.gitignore',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# production
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
tmp/
|
||||||
|
lib/
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.idea/
|
||||||
|
.happypack
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.dia~
|
||||||
|
.ice
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
index.module.scss.d.ts
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'jsconfig',
|
||||||
|
'json',
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"jsx": "react",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"ice": [".ice/index.ts"],
|
||||||
|
"ice/*": [".ice/pages/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'.prettierignore',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
build/
|
||||||
|
tests/
|
||||||
|
demo/
|
||||||
|
.ice/
|
||||||
|
coverage/
|
||||||
|
**/*-min.js
|
||||||
|
**/*.min.js
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'.prettierrc',
|
||||||
|
'js',
|
||||||
|
`
|
||||||
|
const { prettier } = require('@ice/spec');
|
||||||
|
|
||||||
|
module.exports = prettier;
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'index',
|
||||||
|
'jsx',
|
||||||
|
`
|
||||||
|
import React from 'react';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<p className={styles.footer}>
|
||||||
|
<span className={styles.logo}>Alibaba Fusion</span>
|
||||||
|
<br />
|
||||||
|
<span className={styles.copyright}>© 2019-现在 Alibaba Fusion & ICE</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout', 'components', 'Footer'], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'index',
|
||||||
|
'module.scss',
|
||||||
|
`
|
||||||
|
.footer {
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout', 'components', 'Footer'], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'index',
|
||||||
|
'jsx',
|
||||||
|
`
|
||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'ice';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
export default function Logo({ image, text, url }) {
|
||||||
|
return (
|
||||||
|
<div className="logo">
|
||||||
|
<Link to={url || '/'} className={styles.logo}>
|
||||||
|
{image && <img src={image} alt="logo" />}
|
||||||
|
<span>{text}</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout', 'components', 'Logo'], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'index',
|
||||||
|
'module.scss',
|
||||||
|
`
|
||||||
|
.logo{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: $color-text1-1;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
|
||||||
|
&:visited, &:link {
|
||||||
|
color: $color-text1-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout', 'components', 'Logo'], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'index',
|
||||||
|
'jsx',
|
||||||
|
`
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link, withRouter } from 'ice';
|
||||||
|
import { Nav } from '@alifd/next';
|
||||||
|
import { asideMenuConfig } from '../../menuConfig';
|
||||||
|
|
||||||
|
const { SubNav } = Nav;
|
||||||
|
const NavItem = Nav.Item;
|
||||||
|
|
||||||
|
function getNavMenuItems(menusData) {
|
||||||
|
if (!menusData) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return menusData
|
||||||
|
.filter(item => item.name && !item.hideInMenu)
|
||||||
|
.map((item, index) => getSubMenuOrItem(item, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSubMenuOrItem(item, index) {
|
||||||
|
if (item.children && item.children.some(child => child.name)) {
|
||||||
|
const childrenItems = getNavMenuItems(item.children);
|
||||||
|
|
||||||
|
if (childrenItems && childrenItems.length > 0) {
|
||||||
|
const subNav = (
|
||||||
|
<SubNav key={index} icon={item.icon} label={item.name}>
|
||||||
|
{childrenItems}
|
||||||
|
</SubNav>
|
||||||
|
);
|
||||||
|
return subNav;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const navItem = (
|
||||||
|
<NavItem key={item.path} icon={item.icon}>
|
||||||
|
<Link to={item.path}>{item.name}</Link>
|
||||||
|
</NavItem>
|
||||||
|
);
|
||||||
|
return navItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Navigation = (props, context) => {
|
||||||
|
const { location } = props;
|
||||||
|
const { pathname } = location;
|
||||||
|
const { isCollapse } = context;
|
||||||
|
return (
|
||||||
|
<Nav
|
||||||
|
type="primary"
|
||||||
|
selectedKeys={[pathname]}
|
||||||
|
defaultSelectedKeys={[pathname]}
|
||||||
|
embeddable
|
||||||
|
openMode="single"
|
||||||
|
iconOnly={isCollapse}
|
||||||
|
hasArrow={false}
|
||||||
|
mode={isCollapse ? 'popup' : 'inline'}
|
||||||
|
>
|
||||||
|
{getNavMenuItems(asideMenuConfig)}
|
||||||
|
</Nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Navigation.contextTypes = {
|
||||||
|
isCollapse: PropTypes.bool,
|
||||||
|
};
|
||||||
|
const PageNav = withRouter(Navigation);
|
||||||
|
export default PageNav;
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout', 'components', 'PageNav'], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'index',
|
||||||
|
'jsx',
|
||||||
|
`
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Shell, ConfigProvider } from '@alifd/next';
|
||||||
|
import PageNav from './components/PageNav';
|
||||||
|
import Logo from './components/Logo';
|
||||||
|
import Footer from './components/Footer';
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const throttle = function(type, name, obj = window) {
|
||||||
|
let running = false;
|
||||||
|
|
||||||
|
const func = () => {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
running = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
obj.dispatchEvent(new CustomEvent(name));
|
||||||
|
running = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
obj.addEventListener(type, func);
|
||||||
|
};
|
||||||
|
|
||||||
|
throttle('resize', 'optimizedResize');
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default function BasicLayout({ children }) {
|
||||||
|
const getDevice = width => {
|
||||||
|
const isPhone =
|
||||||
|
typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
|
||||||
|
|
||||||
|
if (width < 680 || isPhone) {
|
||||||
|
return 'phone';
|
||||||
|
}
|
||||||
|
if (width < 1280 && width > 680) {
|
||||||
|
return 'tablet';
|
||||||
|
}
|
||||||
|
return 'desktop';
|
||||||
|
};
|
||||||
|
|
||||||
|
const [device, setDevice] = useState(getDevice(NaN));
|
||||||
|
window.addEventListener('optimizedResize', e => {
|
||||||
|
setDevice(getDevice(e && e.target && e.target.innerWidth));
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<ConfigProvider device={device}>
|
||||||
|
<Shell
|
||||||
|
type="dark"
|
||||||
|
style={{
|
||||||
|
minHeight: '100vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Shell.Branding>
|
||||||
|
<Logo
|
||||||
|
image="https://img.alicdn.com/tfs/TB1.ZBecq67gK0jSZFHXXa9jVXa-904-826.png"
|
||||||
|
text="Logo"
|
||||||
|
/>
|
||||||
|
</Shell.Branding>
|
||||||
|
<Shell.Navigation
|
||||||
|
direction="hoz"
|
||||||
|
style={{
|
||||||
|
marginRight: 10,
|
||||||
|
}}
|
||||||
|
></Shell.Navigation>
|
||||||
|
<Shell.Action></Shell.Action>
|
||||||
|
<Shell.Navigation>
|
||||||
|
<PageNav />
|
||||||
|
</Shell.Navigation>
|
||||||
|
|
||||||
|
<Shell.Content>{children}</Shell.Content>
|
||||||
|
<Shell.Footer>
|
||||||
|
<Footer />
|
||||||
|
</Shell.Footer>
|
||||||
|
</Shell>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout'], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'menuConfig',
|
||||||
|
'js',
|
||||||
|
`
|
||||||
|
const headerMenuConfig = [];
|
||||||
|
const asideMenuConfig = [
|
||||||
|
{
|
||||||
|
name: 'Dashboard',
|
||||||
|
path: '/',
|
||||||
|
icon: 'smile',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export { headerMenuConfig, asideMenuConfig };
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout'], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'.stylelintignore',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
# 忽略目录
|
||||||
|
build/
|
||||||
|
tests/
|
||||||
|
demo/
|
||||||
|
|
||||||
|
# node 覆盖率文件
|
||||||
|
coverage/
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'.stylelintrc',
|
||||||
|
'js',
|
||||||
|
`
|
||||||
|
const { stylelint } = require('@ice/spec');
|
||||||
|
|
||||||
|
module.exports = stylelint;
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import ResultFile from '@/model/ResultFile';
|
||||||
|
import { IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], IResultFile] {
|
||||||
|
const file = new ResultFile(
|
||||||
|
'tsconfig',
|
||||||
|
'json',
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"buildOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "build",
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "es6",
|
||||||
|
"jsx": "react",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"lib": ["es6", "dom"],
|
||||||
|
"sourceMap": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"rootDir": "./",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"importHelpers": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"ice": [".ice/index.ts"],
|
||||||
|
"ice/*": [".ice/pages/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/*", ".ice"],
|
||||||
|
"exclude": ["node_modules", "build", "public"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
import ResultDir from '@/model/ResultDir';
|
||||||
|
import { IProjectTemplate, IResultDir, IResultFile } from '@/types';
|
||||||
|
|
||||||
|
import file12 from './files/abc.json';
|
||||||
|
import file11 from './files/build.json';
|
||||||
|
import file10 from './files/editorconfig';
|
||||||
|
import file9 from './files/eslintignore';
|
||||||
|
import file8 from './files/eslintrc.js';
|
||||||
|
import file7 from './files/gitignore';
|
||||||
|
import file6 from './files/jsconfig.json';
|
||||||
|
import file5 from './files/prettierignore';
|
||||||
|
import file4 from './files/prettierrc.js';
|
||||||
|
import file13 from './files/README.md';
|
||||||
|
import file16 from './files/src/layouts/BasicLayout/components/Footer/index.jsx';
|
||||||
|
import file17 from './files/src/layouts/BasicLayout/components/Footer/index.module.scss';
|
||||||
|
import file18 from './files/src/layouts/BasicLayout/components/Logo/index.jsx';
|
||||||
|
import file19 from './files/src/layouts/BasicLayout/components/Logo/index.module.scss';
|
||||||
|
import file20 from './files/src/layouts/BasicLayout/components/PageNav/index.jsx';
|
||||||
|
import file14 from './files/src/layouts/BasicLayout/index.jsx';
|
||||||
|
import file15 from './files/src/layouts/BasicLayout/menuConfig.js';
|
||||||
|
import file3 from './files/stylelintignore';
|
||||||
|
import file2 from './files/stylelintrc.js';
|
||||||
|
import file1 from './files/tsconfig.json';
|
||||||
|
|
||||||
|
type FuncFileGenerator = () => [string[], IResultFile];
|
||||||
|
|
||||||
|
function insertFile(root: IResultDir, path: string[], file: IResultFile) {
|
||||||
|
let current: IResultDir = root;
|
||||||
|
path.forEach(pathNode => {
|
||||||
|
const dir = current.dirs.find(d => d.name === pathNode);
|
||||||
|
if (dir) {
|
||||||
|
current = dir;
|
||||||
|
} else {
|
||||||
|
const newDir = new ResultDir(pathNode);
|
||||||
|
current.addDirectory(newDir);
|
||||||
|
current = newDir;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
current.addFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runFileGenerator(root: IResultDir, fun: FuncFileGenerator) {
|
||||||
|
const [path, file] = fun();
|
||||||
|
insertFile(root, path, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
const icejsTemplate: IProjectTemplate = {
|
||||||
|
slots: {
|
||||||
|
components: {
|
||||||
|
path: ['src', 'components'],
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
path: ['src', 'pages'],
|
||||||
|
},
|
||||||
|
router: {
|
||||||
|
path: ['src'],
|
||||||
|
fileName: 'routes',
|
||||||
|
},
|
||||||
|
entry: {
|
||||||
|
path: ['src'],
|
||||||
|
fileName: 'app',
|
||||||
|
},
|
||||||
|
constants: {
|
||||||
|
path: ['src'],
|
||||||
|
fileName: 'constants',
|
||||||
|
},
|
||||||
|
utils: {
|
||||||
|
path: ['src'],
|
||||||
|
fileName: 'utils',
|
||||||
|
},
|
||||||
|
i18n: {
|
||||||
|
path: ['src'],
|
||||||
|
fileName: 'i18n',
|
||||||
|
},
|
||||||
|
globalStyle: {
|
||||||
|
path: ['src'],
|
||||||
|
fileName: 'global',
|
||||||
|
},
|
||||||
|
htmlEntry: {
|
||||||
|
path: ['public'],
|
||||||
|
fileName: 'index',
|
||||||
|
},
|
||||||
|
packageJSON: {
|
||||||
|
path: [],
|
||||||
|
fileName: 'package',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
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, file12);
|
||||||
|
runFileGenerator(root, file13);
|
||||||
|
runFileGenerator(root, file14);
|
||||||
|
runFileGenerator(root, file15);
|
||||||
|
runFileGenerator(root, file16);
|
||||||
|
runFileGenerator(root, file17);
|
||||||
|
runFileGenerator(root, file18);
|
||||||
|
runFileGenerator(root, file19);
|
||||||
|
runFileGenerator(root, file20);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default icejsTemplate;
|
||||||
66
packages/code-generator/src/plugins/project/i18n.ts
Normal file
66
packages/code-generator/src/plugins/project/i18n.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '@/const/generator';
|
||||||
|
import { generateCompositeType } from '@/plugins/utils/compositeType';
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
if (ir.i18n) {
|
||||||
|
const [, i18nStr] = generateCompositeType(ir.i18n);
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
content: `
|
||||||
|
const i18nConfig = ${i18nStr};
|
||||||
|
let locale = 'en_US';
|
||||||
|
|
||||||
|
const changeLocale = (target) => {
|
||||||
|
locale = target;
|
||||||
|
};
|
||||||
|
|
||||||
|
const i18n = key => i18nConfig && i18nConfig[locale] && i18nConfig[locale][key] || '';
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `
|
||||||
|
export {
|
||||||
|
changeLocale,
|
||||||
|
i18n,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
90
packages/code-generator/src/plugins/project/utils.ts
Normal file
90
packages/code-generator/src/plugins/project/utils.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '@/const/generator';
|
||||||
|
import { generateCompositeType } from '@/plugins/utils/compositeType';
|
||||||
|
// import { } from '@/plugins/utils/jsExpression';
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IUtilInfo,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IUtilInfo;
|
||||||
|
|
||||||
|
if (ir.utils) {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `
|
||||||
|
export default {
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
ir.utils.forEach(util => {
|
||||||
|
if (util.type === 'function') {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
content: `
|
||||||
|
const ${util.name} = ${util.content};
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `
|
||||||
|
${util.name},
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
45
packages/code-generator/src/plugins/utils/compositeType.ts
Normal file
45
packages/code-generator/src/plugins/utils/compositeType.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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];
|
||||||
|
}
|
||||||
39
packages/code-generator/src/plugins/utils/jsExpression.ts
Normal file
39
packages/code-generator/src/plugins/utils/jsExpression.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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'
|
||||||
|
);
|
||||||
|
}
|
||||||
89
packages/code-generator/src/publisher/disk/index.ts
Normal file
89
packages/code-generator/src/publisher/disk/index.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { CodeGeneratorError, IResultDir } from '@/types';
|
||||||
|
|
||||||
|
export type PublisherFactory<T, U> = (configuration?: Partial<T>) => U;
|
||||||
|
|
||||||
|
export interface IPublisher<T, U> {
|
||||||
|
publish: (options?: T) => Promise<IPublisherResponse<U>>;
|
||||||
|
getProject: () => IResultDir | void;
|
||||||
|
setProject: (project: IResultDir) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPublisherFactoryParams {
|
||||||
|
project?: IResultDir;
|
||||||
|
}
|
||||||
|
export interface IPublisherResponse<T> {
|
||||||
|
success: boolean;
|
||||||
|
payload?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
import { writeFolder } from './utils';
|
||||||
|
|
||||||
|
export interface IDiskFactoryParams extends IPublisherFactoryParams {
|
||||||
|
outputPath?: string;
|
||||||
|
projectSlug?: string;
|
||||||
|
createProjectFolder?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDiskPublisher extends IPublisher<IDiskFactoryParams, string> {
|
||||||
|
getOutputPath: () => string;
|
||||||
|
setOutputPath: (path: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDiskPublisher: PublisherFactory<
|
||||||
|
IDiskFactoryParams,
|
||||||
|
IDiskPublisher
|
||||||
|
> = (params: IDiskFactoryParams = {}): IDiskPublisher => {
|
||||||
|
let { project, outputPath = './' } = params;
|
||||||
|
|
||||||
|
const getProject = (): IResultDir => {
|
||||||
|
if (!project) {
|
||||||
|
throw new CodeGeneratorError('Missing Project');
|
||||||
|
}
|
||||||
|
return project;
|
||||||
|
};
|
||||||
|
const setProject = (projectToSet: IResultDir): void => {
|
||||||
|
project = projectToSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOutputPath = (): string => {
|
||||||
|
return outputPath;
|
||||||
|
};
|
||||||
|
const setOutputPath = (path: string): void => {
|
||||||
|
outputPath = path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const publish = async (options: IDiskFactoryParams = {}) => {
|
||||||
|
const projectToPublish = options.project || project;
|
||||||
|
if (!projectToPublish) {
|
||||||
|
throw new CodeGeneratorError('Missing Project');
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectOutputPath = options.outputPath || outputPath;
|
||||||
|
const overrideProjectSlug = options.projectSlug || params.projectSlug;
|
||||||
|
const createProjectFolder =
|
||||||
|
options.createProjectFolder || params.createProjectFolder;
|
||||||
|
|
||||||
|
if (overrideProjectSlug) {
|
||||||
|
projectToPublish.name = overrideProjectSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await writeFolder(
|
||||||
|
projectToPublish,
|
||||||
|
projectOutputPath,
|
||||||
|
createProjectFolder,
|
||||||
|
);
|
||||||
|
return { success: true, payload: projectOutputPath };
|
||||||
|
} catch (error) {
|
||||||
|
throw new CodeGeneratorError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
publish,
|
||||||
|
getProject,
|
||||||
|
setProject,
|
||||||
|
getOutputPath,
|
||||||
|
setOutputPath,
|
||||||
|
};
|
||||||
|
};
|
||||||
71
packages/code-generator/src/publisher/disk/utils.ts
Normal file
71
packages/code-generator/src/publisher/disk/utils.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { existsSync, mkdir, writeFile } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { IResultDir, IResultFile } from '@/types';
|
||||||
|
|
||||||
|
export const writeFolder = async (
|
||||||
|
folder: IResultDir,
|
||||||
|
currentPath: string,
|
||||||
|
createProjectFolder = true,
|
||||||
|
): Promise<void> => {
|
||||||
|
const { name, files, dirs } = folder;
|
||||||
|
|
||||||
|
const folderPath = createProjectFolder
|
||||||
|
? join(currentPath, name)
|
||||||
|
: currentPath;
|
||||||
|
|
||||||
|
if (!existsSync(folderPath)) {
|
||||||
|
await createDirectory(folderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
writeFilesToFolder(folderPath, files),
|
||||||
|
writeSubFoldersToFolder(folderPath, dirs),
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeFilesToFolder = async (
|
||||||
|
folderPath: string,
|
||||||
|
files: IResultFile[],
|
||||||
|
): Promise<void> => {
|
||||||
|
const promises = files.map(file => {
|
||||||
|
const fileName = file.ext ? `${file.name}.${file.ext}` : file.name;
|
||||||
|
const filePath = join(folderPath, fileName);
|
||||||
|
return writeContentToFile(filePath, file.content);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeSubFoldersToFolder = async (
|
||||||
|
folderPath: string,
|
||||||
|
subFolders: IResultDir[],
|
||||||
|
): Promise<void> => {
|
||||||
|
const promises = subFolders.map(subFolder => {
|
||||||
|
return writeFolder(subFolder, folderPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDirectory = (pathToDir: string): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
mkdir(pathToDir, { recursive: true }, err => {
|
||||||
|
err ? reject(err) : resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeContentToFile = (
|
||||||
|
filePath: string,
|
||||||
|
fileContent: string,
|
||||||
|
encoding: string = 'utf8',
|
||||||
|
): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
writeFile(filePath, fileContent, encoding, err => {
|
||||||
|
err ? reject(err) : resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
60
packages/code-generator/src/solutions/icejs.ts
Normal file
60
packages/code-generator/src/solutions/icejs.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { IProjectBuilder } from '@/types';
|
||||||
|
|
||||||
|
import { createProjectBuilder } from '@/generator/ProjectBuilder';
|
||||||
|
|
||||||
|
import esmodule from '@/plugins/common/esmodule';
|
||||||
|
import containerClass from '@/plugins/component/react/containerClass';
|
||||||
|
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 iceJsEntry from '@/plugins/project/framework/icejs/plugins/entry';
|
||||||
|
import iceJsEntryHtml from '@/plugins/project/framework/icejs/plugins/entryHtml';
|
||||||
|
import iceJsGlobalStyle from '@/plugins/project/framework/icejs/plugins/globalStyle';
|
||||||
|
import iceJsPackageJSON from '@/plugins/project/framework/icejs/plugins/packageJSON';
|
||||||
|
import iceJsRouter from '@/plugins/project/framework/icejs/plugins/router';
|
||||||
|
import template from '@/plugins/project/framework/icejs/template';
|
||||||
|
import i18n from '@/plugins/project/i18n';
|
||||||
|
import utils from '@/plugins/project/utils';
|
||||||
|
|
||||||
|
export default function createIceJsProjectBuilder(): IProjectBuilder {
|
||||||
|
return createProjectBuilder({
|
||||||
|
template,
|
||||||
|
plugins: {
|
||||||
|
components: [
|
||||||
|
reactCommonDeps,
|
||||||
|
esmodule,
|
||||||
|
containerClass,
|
||||||
|
// containerInjectUtils,
|
||||||
|
containerInitState,
|
||||||
|
containerLifeCycle,
|
||||||
|
containerMethod,
|
||||||
|
jsx,
|
||||||
|
css,
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
reactCommonDeps,
|
||||||
|
esmodule,
|
||||||
|
containerClass,
|
||||||
|
// containerInjectUtils,
|
||||||
|
containerInitState,
|
||||||
|
containerLifeCycle,
|
||||||
|
containerMethod,
|
||||||
|
jsx,
|
||||||
|
css,
|
||||||
|
],
|
||||||
|
router: [esmodule, iceJsRouter],
|
||||||
|
entry: [iceJsEntry],
|
||||||
|
constants: [constants],
|
||||||
|
utils: [esmodule, utils],
|
||||||
|
i18n: [i18n],
|
||||||
|
globalStyle: [iceJsGlobalStyle],
|
||||||
|
htmlEntry: [iceJsEntryHtml],
|
||||||
|
packageJSON: [iceJsPackageJSON],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
143
packages/code-generator/src/types/core.ts
Normal file
143
packages/code-generator/src/types/core.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import {
|
||||||
|
IBasicSchema,
|
||||||
|
IParseResult,
|
||||||
|
IProjectSchema,
|
||||||
|
IResultDir,
|
||||||
|
IResultFile,
|
||||||
|
} from './index';
|
||||||
|
|
||||||
|
export enum FileType {
|
||||||
|
CSS = 'css',
|
||||||
|
SCSS = 'scss',
|
||||||
|
HTML = 'html',
|
||||||
|
JS = 'js',
|
||||||
|
JSX = 'jsx',
|
||||||
|
JSON = 'json',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ChunkType {
|
||||||
|
AST = 'ast',
|
||||||
|
STRING = 'string',
|
||||||
|
JSON = 'json',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PluginType {
|
||||||
|
COMPONENT = 'component',
|
||||||
|
UTILS = 'utils',
|
||||||
|
I18N = 'i18n',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChunkContent = string | any;
|
||||||
|
export type CodeGeneratorFunction<T> = (content: T) => string;
|
||||||
|
|
||||||
|
export interface ICodeChunk {
|
||||||
|
type: ChunkType;
|
||||||
|
fileType: FileType;
|
||||||
|
name: string;
|
||||||
|
subModule?: string;
|
||||||
|
content: ChunkContent;
|
||||||
|
linkAfter: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBaseCodeStruct {
|
||||||
|
chunks: ICodeChunk[];
|
||||||
|
depNames: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICodeStruct extends IBaseCodeStruct {
|
||||||
|
ir: any;
|
||||||
|
chunks: ICodeChunk[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BuilderComponentPlugin = (
|
||||||
|
initStruct: ICodeStruct,
|
||||||
|
) => Promise<ICodeStruct>;
|
||||||
|
|
||||||
|
export interface IChunkBuilder {
|
||||||
|
run(
|
||||||
|
ir: any,
|
||||||
|
initialStructure?: ICodeStruct,
|
||||||
|
): Promise<{ chunks: ICodeChunk[][] }>;
|
||||||
|
getPlugins(): BuilderComponentPlugin[];
|
||||||
|
addPlugin(plugin: BuilderComponentPlugin): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICodeBuilder {
|
||||||
|
link(chunkDefinitions: ICodeChunk[]): string;
|
||||||
|
generateByType(type: string, content: unknown): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICompiledModule {
|
||||||
|
files: IResultFile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModuleBuilder {
|
||||||
|
generateModule: (input: unknown) => Promise<ICompiledModule>;
|
||||||
|
linkCodeChunks: (
|
||||||
|
chunks: Record<string, ICodeChunk[]>,
|
||||||
|
fileName: string,
|
||||||
|
) => IResultFile[];
|
||||||
|
addPlugin: (plugin: BuilderComponentPlugin) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 引擎对外接口
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface ICodeGenerator
|
||||||
|
*/
|
||||||
|
export interface ICodeGenerator {
|
||||||
|
/**
|
||||||
|
* 出码接口,把 Schema 转换成代码文件系统描述
|
||||||
|
*
|
||||||
|
* @param {(IBasicSchema)} schema 传入的 Schema
|
||||||
|
* @returns {IResultDir}
|
||||||
|
* @memberof ICodeGenerator
|
||||||
|
*/
|
||||||
|
toCode(schema: IBasicSchema): Promise<IResultDir>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISchemaParser {
|
||||||
|
validate(schema: IBasicSchema): boolean;
|
||||||
|
parse(schema: IBasicSchema): IParseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectTemplate {
|
||||||
|
slots: IProjectSlots;
|
||||||
|
generateTemplate(): IResultDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectSlot {
|
||||||
|
path: string[];
|
||||||
|
fileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectSlots {
|
||||||
|
components: IProjectSlot;
|
||||||
|
pages: IProjectSlot;
|
||||||
|
router: IProjectSlot;
|
||||||
|
entry: IProjectSlot;
|
||||||
|
constants?: IProjectSlot;
|
||||||
|
utils?: IProjectSlot;
|
||||||
|
i18n?: IProjectSlot;
|
||||||
|
globalStyle: IProjectSlot;
|
||||||
|
htmlEntry: IProjectSlot;
|
||||||
|
packageJSON: IProjectSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectPlugins {
|
||||||
|
components: BuilderComponentPlugin[];
|
||||||
|
pages: BuilderComponentPlugin[];
|
||||||
|
router: BuilderComponentPlugin[];
|
||||||
|
entry: BuilderComponentPlugin[];
|
||||||
|
constants?: BuilderComponentPlugin[];
|
||||||
|
utils?: BuilderComponentPlugin[];
|
||||||
|
i18n?: BuilderComponentPlugin[];
|
||||||
|
globalStyle: BuilderComponentPlugin[];
|
||||||
|
htmlEntry: BuilderComponentPlugin[];
|
||||||
|
packageJSON: BuilderComponentPlugin[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectBuilder {
|
||||||
|
generateProject(schema: IProjectSchema): Promise<IResultDir>;
|
||||||
|
}
|
||||||
36
packages/code-generator/src/types/deps.ts
Normal file
36
packages/code-generator/src/types/deps.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 外部依赖描述
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IExternalDependency
|
||||||
|
*/
|
||||||
|
export interface IExternalDependency extends IDependency {
|
||||||
|
package: string; // 组件包的名称
|
||||||
|
version: string; // 组件包的版本
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum InternalDependencyType {
|
||||||
|
PAGE = 'pages',
|
||||||
|
BLOCK = 'components',
|
||||||
|
COMPONENT = 'components',
|
||||||
|
UTILS = 'utils',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DependencyType {
|
||||||
|
External = 'External',
|
||||||
|
Internal = 'Internal',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInternalDependency extends IDependency {
|
||||||
|
type: InternalDependencyType;
|
||||||
|
moduleName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDependency {
|
||||||
|
destructuring: boolean; // 组件是否是解构方式方式导出
|
||||||
|
exportName: string; // 导出命名
|
||||||
|
subName?: string; // 下标子组件名称
|
||||||
|
main?: string; // 包导出组件入口文件路径 /lib/input
|
||||||
|
dependencyType?: DependencyType; // 依赖类型 内/外
|
||||||
|
importName?: string; // 导入后名称
|
||||||
|
}
|
||||||
20
packages/code-generator/src/types/error.ts
Normal file
20
packages/code-generator/src/types/error.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export class CodeGeneratorError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line: max-classes-per-file
|
||||||
|
export class ComponentValidationError extends CodeGeneratorError {
|
||||||
|
constructor(errorString: string) {
|
||||||
|
super(errorString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line: max-classes-per-file
|
||||||
|
export class CompatibilityError extends CodeGeneratorError {
|
||||||
|
constructor(errorString: string) {
|
||||||
|
super(errorString);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/code-generator/src/types/index.ts
Normal file
6
packages/code-generator/src/types/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from './core';
|
||||||
|
export * from './deps';
|
||||||
|
export * from './error';
|
||||||
|
export * from './result';
|
||||||
|
export * from './schema';
|
||||||
|
export * from './intermediate';
|
||||||
64
packages/code-generator/src/types/intermediate.ts
Normal file
64
packages/code-generator/src/types/intermediate.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import {
|
||||||
|
IAppConfig,
|
||||||
|
IAppMeta,
|
||||||
|
IContainerNodeItem,
|
||||||
|
IDependency,
|
||||||
|
II18nMap,
|
||||||
|
IInternalDependency,
|
||||||
|
IUtilItem,
|
||||||
|
} from './index';
|
||||||
|
|
||||||
|
export interface IParseResult {
|
||||||
|
containers: IContainerInfo[];
|
||||||
|
globalUtils?: IUtilInfo;
|
||||||
|
globalI18n?: II18nMap;
|
||||||
|
globalRouter?: IRouterInfo;
|
||||||
|
project?: IProjectInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IContainerInfo extends IContainerNodeItem, IWithDependency {
|
||||||
|
componentName: string;
|
||||||
|
containerType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWithDependency {
|
||||||
|
deps?: IDependency[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUtilInfo extends IWithDependency {
|
||||||
|
utils: IUtilItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRouterInfo extends IWithDependency {
|
||||||
|
routes: Array<{
|
||||||
|
path: string;
|
||||||
|
componentName: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectInfo {
|
||||||
|
config: IAppConfig;
|
||||||
|
meta: IAppMeta;
|
||||||
|
css?: string;
|
||||||
|
constants?: Record<string, string>;
|
||||||
|
i18n?: II18nMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From meta
|
||||||
|
* page title
|
||||||
|
* router
|
||||||
|
* spmb
|
||||||
|
*
|
||||||
|
* Utils
|
||||||
|
*
|
||||||
|
* constants
|
||||||
|
*
|
||||||
|
* i18n
|
||||||
|
*
|
||||||
|
* components
|
||||||
|
*
|
||||||
|
* pages
|
||||||
|
*
|
||||||
|
* layout
|
||||||
|
*/
|
||||||
88
packages/code-generator/src/types/result.ts
Normal file
88
packages/code-generator/src/types/result.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 导出内容结构,文件夹
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IResultDir
|
||||||
|
*/
|
||||||
|
export interface IResultDir {
|
||||||
|
/**
|
||||||
|
* 文件夹名称,Root 名称默认为 .
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof IResultDir
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* 子目录
|
||||||
|
*
|
||||||
|
* @type {IResultDir[]}
|
||||||
|
* @memberof IResultDir
|
||||||
|
*/
|
||||||
|
dirs: IResultDir[];
|
||||||
|
/**
|
||||||
|
* 文件夹内文件
|
||||||
|
*
|
||||||
|
* @type {IResultFile[]}
|
||||||
|
* @memberof IResultDir
|
||||||
|
*/
|
||||||
|
files: IResultFile[];
|
||||||
|
/**
|
||||||
|
* 添加文件
|
||||||
|
*
|
||||||
|
* @param {IResultFile} file
|
||||||
|
* @memberof IResultDir
|
||||||
|
*/
|
||||||
|
addFile(file: IResultFile): void;
|
||||||
|
/**
|
||||||
|
* 添加子目录
|
||||||
|
*
|
||||||
|
* @param {IResultDir} dir
|
||||||
|
* @memberof IResultDir
|
||||||
|
*/
|
||||||
|
addDirectory(dir: IResultDir): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出内容,对文件的描述
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IResultFile
|
||||||
|
*/
|
||||||
|
export interface IResultFile {
|
||||||
|
/**
|
||||||
|
* 文件名
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof IResultFile
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* 文件类型扩展名,例如 .js .less
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof IResultFile
|
||||||
|
*/
|
||||||
|
ext: string;
|
||||||
|
/**
|
||||||
|
* 文件内容
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof IResultFile
|
||||||
|
*/
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPackageJSON {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
description?: string;
|
||||||
|
dependencies: Record<string, string>;
|
||||||
|
devDependencies: Record<string, string>;
|
||||||
|
scripts?: Record<string, string>;
|
||||||
|
engines?: Record<string, string>;
|
||||||
|
repository?: {
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
private?: boolean;
|
||||||
|
}
|
||||||
255
packages/code-generator/src/types/schema.ts
Normal file
255
packages/code-generator/src/types/schema.ts
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
// 搭建基础协议、搭建入料协议的数据规范
|
||||||
|
import { IExternalDependency } from './index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议 - 函数表达式
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IJSExpression
|
||||||
|
*/
|
||||||
|
export interface IJSExpression {
|
||||||
|
type: 'JSExpression';
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON 基本类型
|
||||||
|
export interface IJSONObject {
|
||||||
|
[key: string]: JSONValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JSONValue =
|
||||||
|
| boolean
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| null
|
||||||
|
| JSONArray
|
||||||
|
| IJSONObject;
|
||||||
|
export type JSONArray = JSONValue[];
|
||||||
|
|
||||||
|
export type CompositeArray = CompositeValue[];
|
||||||
|
export interface ICompositeObject {
|
||||||
|
[key: string]: CompositeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复合类型
|
||||||
|
export type CompositeValue =
|
||||||
|
| JSONValue
|
||||||
|
| IJSExpression
|
||||||
|
| CompositeArray
|
||||||
|
| ICompositeObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议 - 多语言描述
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface II18nMap
|
||||||
|
*/
|
||||||
|
export interface II18nMap {
|
||||||
|
[lang: string]: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IBasicSchema
|
||||||
|
*/
|
||||||
|
export interface IBasicSchema {
|
||||||
|
version: string; // 当前协议版本号
|
||||||
|
componentsMap: IComponentsMapItem[]; // 组件映射关系
|
||||||
|
componentsTree: Array<IContainerNodeItem | IComponentNodeItem>; // 描述模版/页面/区块/低代码业务组件的组件树 低代码业务组件树描述,固定长度为1,且顶层为低代码业务组件容器描述
|
||||||
|
utils?: IUtilItem[]; // 工具类扩展映射关系 低代码业务组件不包含
|
||||||
|
i18n?: II18nMap; // 国际化语料
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProjectSchema extends IBasicSchema {
|
||||||
|
constants: Record<string, string>; // 应用范围内的全局常量;
|
||||||
|
css: string; // 应用范围内的全局样式;
|
||||||
|
config: IAppConfig; // 当前应用配置信息
|
||||||
|
meta: IAppMeta; // 当前应用元数据信息
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议 - 单个组件描述
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IComponentsMapItem
|
||||||
|
*/
|
||||||
|
export interface IComponentsMapItem extends IExternalDependency {
|
||||||
|
componentName: string; // 组件名称
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUtilItem {
|
||||||
|
name: string;
|
||||||
|
type: 'npm' | 'tnpm' | 'function';
|
||||||
|
content: IExternalDependency | IJSExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInlineStyle {
|
||||||
|
[cssAttribute: string]: string | number | IJSExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChildNodeItem = string | IJSExpression | IComponentNodeItem;
|
||||||
|
export type ChildNodeType = ChildNodeItem | ChildNodeItem[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议 - 单个组件树节点描述
|
||||||
|
* 转换成一个 .jsx 文件内 React Class 类 render 函数返回的 jsx 代码
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IComponentNodeItem
|
||||||
|
*/
|
||||||
|
export interface IComponentNodeItem {
|
||||||
|
// TODO: 不需要 id 字段,暂时简单兼容
|
||||||
|
id?: string;
|
||||||
|
componentName: string; // 组件名称 必填、首字母大写
|
||||||
|
props: {
|
||||||
|
className?: string; // 组件样式类名
|
||||||
|
style?: IInlineStyle; // 组件内联样式
|
||||||
|
[propName: string]: any; // 业务属性
|
||||||
|
}; // 组件属性对象
|
||||||
|
condition?: CompositeValue; // 渲染条件
|
||||||
|
loop?: CompositeValue; // 循环数据
|
||||||
|
loopArgs?: [string, string]; // 循环迭代对象、索引名称 ["item", "index"]
|
||||||
|
children?: ChildNodeType; // 子节点
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议 - 单个容器节点描述
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IContainerNodeItem
|
||||||
|
* @extends {IComponentNodeItem}
|
||||||
|
*/
|
||||||
|
export interface IContainerNodeItem extends IComponentNodeItem {
|
||||||
|
componentName: string; // 'Page' | 'Block' | 'Component' 组件类型 必填、首字母大写
|
||||||
|
fileName: string; // 文件名称 必填、英文
|
||||||
|
defaultProps?: {
|
||||||
|
[propName: string]: any; // 业务属性
|
||||||
|
};
|
||||||
|
state?: {
|
||||||
|
[stateName: string]: any; // 容器初始数据
|
||||||
|
};
|
||||||
|
css: string; // 样式文件 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,在对应容器组件生成的 .jsx 文件中 import 引入;
|
||||||
|
/**
|
||||||
|
* LifeCycle
|
||||||
|
* • constructor(props, context)
|
||||||
|
* • 说明:初始化渲染时执行,常用于设置state值;
|
||||||
|
* • render()
|
||||||
|
* • 说明:执行于容器组件React Class的render方法最前,常用于计算变量挂载到this对象上,供props上属性绑定。此render()方法不需要设置return返回值。
|
||||||
|
* • componentDidMount()
|
||||||
|
* • componentDidUpdate(prevProps, prevState, snapshot)
|
||||||
|
* • componentWillUnmount()
|
||||||
|
* • componentDidCatch(error, info)
|
||||||
|
*/
|
||||||
|
lifeCycles?: {
|
||||||
|
constructor?: IJSExpression;
|
||||||
|
render?: IJSExpression;
|
||||||
|
componentDidMount?: IJSExpression;
|
||||||
|
componentDidUpdate?: IJSExpression;
|
||||||
|
componentWillUnmount?: IJSExpression;
|
||||||
|
componentDidCatch?: IJSExpression;
|
||||||
|
}; // 生命周期Hook方法
|
||||||
|
methods?: {
|
||||||
|
[methodName: string]: IJSExpression;
|
||||||
|
}; // 自定义方法设置
|
||||||
|
dataSource?: IDataSource; // 异步数据源配置
|
||||||
|
meta?: IBasicMeta | IPageMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议 - 数据源
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IDataSource
|
||||||
|
*/
|
||||||
|
export interface IDataSource {
|
||||||
|
list: IDataSourceConfig[]; // 成为为单个请求配置
|
||||||
|
/**
|
||||||
|
* 参数:为dataMap对象,key:数据id, value: 单个请求结果
|
||||||
|
* 返回值:数据对象data,将会在渲染引擎和schemaToCode中通过调用this.setState(...)将返回的数据对象生效到state中;
|
||||||
|
* 支持返回一个Promise,通过resolve(返回数据),常用于串型发送请求场景,配合this.dataSourceMap[oneRequest.id].load()使用;
|
||||||
|
*/
|
||||||
|
dataHandler?: IJSExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议 - 数据源单个配置
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IDataSourceConfig
|
||||||
|
*/
|
||||||
|
export interface IDataSourceConfig {
|
||||||
|
id: string; // 数据请求ID标识
|
||||||
|
isInit: boolean; // 是否为初始数据 支持表达式 值为true时,将在组件初始化渲染时自动发送当前数据请求
|
||||||
|
type: 'fetch' | 'mtop' | 'jsonp' | 'custom' | 'doServer'; // 数据请求类型
|
||||||
|
requestHandler?: IJSExpression; // 自定义扩展的外部请求处理器 仅type='custom'时生效
|
||||||
|
options?: IFetchOptions; // 请求参数配置 每种请求类型对应不同参数
|
||||||
|
dataHandler?: IJSExpression; // 数据结果处理函数,形如:(data, err) => Object
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搭建基础协议 - 请求参数配置
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IFetchOptions
|
||||||
|
*/
|
||||||
|
export interface IFetchOptions {
|
||||||
|
uri: string; // 请求地址 支持表达式
|
||||||
|
params?: {
|
||||||
|
// 请求参数
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
method: 'GET' | 'POST';
|
||||||
|
isCors?: boolean; // 是否支持跨域,对应credentials = 'include'
|
||||||
|
timeout?: number; // 超时时长
|
||||||
|
headers?: {
|
||||||
|
// 自定义请求头
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBasicMeta {
|
||||||
|
title: string; // 标题描述
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPageMeta extends IBasicMeta {
|
||||||
|
router: string; // 页面路由
|
||||||
|
spmb?: string; // spm
|
||||||
|
}
|
||||||
|
|
||||||
|
// "theme": {
|
||||||
|
// //for Fusion use dpl defined
|
||||||
|
// "package": "@alife/theme-fusion",
|
||||||
|
// "version": "^0.1.0",
|
||||||
|
|
||||||
|
// //for Antd use variable
|
||||||
|
// "primary": "#ff9966"
|
||||||
|
// }
|
||||||
|
|
||||||
|
// "layout": {
|
||||||
|
// "componentName": "BasicLayout",
|
||||||
|
// "props": {
|
||||||
|
// "logo": "...",
|
||||||
|
// "name": "测试网站"
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
export interface IAppConfig {
|
||||||
|
sdkVersion: string; // 渲染模块版本
|
||||||
|
historyMode: 'brower' | 'hash'; // 浏览器路由:brower 哈希路由:hash
|
||||||
|
targetRootID: string; // 渲染根节点 ID
|
||||||
|
layout: IComponentNodeItem;
|
||||||
|
theme: object; // 主题配置,根据接入的主题模块不同
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppMeta {
|
||||||
|
name: string; // 应用中文名称
|
||||||
|
git_group?: string; // 应用对应git分组名
|
||||||
|
project_name?: string; // 应用对应git的project名称
|
||||||
|
description?: string; // 应用描述
|
||||||
|
spma?: string; // 应用spma A位信息
|
||||||
|
creator?: string; // author
|
||||||
|
}
|
||||||
35
packages/code-generator/src/utils/children.ts
Normal file
35
packages/code-generator/src/utils/children.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
packages/code-generator/src/utils/common.ts
Normal file
28
packages/code-generator/src/utils/common.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import changeCase from 'change-case';
|
||||||
|
import short from 'short-uuid';
|
||||||
|
|
||||||
|
// Doc: https://www.npmjs.com/package/change-case
|
||||||
|
|
||||||
|
export function camel2dash(input: string): string {
|
||||||
|
return changeCase.paramCase(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转为驼峰
|
||||||
|
*/
|
||||||
|
export function camelize(str: string): string {
|
||||||
|
return changeCase.camelCase(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateID(): string {
|
||||||
|
return short.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function upperCaseFirst(inputValue: string): string {
|
||||||
|
return changeCase.upperCaseFirst(inputValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uniqueArray<T>(arr: T[]) {
|
||||||
|
const uniqueItems = [...new Set<T>(arr)];
|
||||||
|
return uniqueItems;
|
||||||
|
}
|
||||||
17
packages/code-generator/tsconfig.json
Normal file
17
packages/code-generator/tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"outDir": "./lib",
|
||||||
|
"lib": [
|
||||||
|
"es6"
|
||||||
|
],
|
||||||
|
"types": ["node"],
|
||||||
|
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import { PluginProps } from '../../framework/definitions';
|
|||||||
const Save: React.FC<PluginProps> = (props): React.ReactElement => {
|
const Save: React.FC<PluginProps> = (props): React.ReactElement => {
|
||||||
const handleClick = (): void => {
|
const handleClick = (): void => {
|
||||||
console.log('save data:', props.editor.designer.currentDocument.schema);
|
console.log('save data:', props.editor.designer.currentDocument.schema);
|
||||||
|
console.log('save data json:', JSON.stringify(props.editor.designer.currentDocument.schema));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user