mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-02-21 16:30:32 +00:00
chore: 🤖 delete es
This commit is contained in:
commit
0ca9ae7c28
@ -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/**/*"
|
||||
]
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
# demo component
|
||||
|
||||
t-s-demo
|
||||
|
||||
intro component
|
||||
|
||||
## API
|
||||
|
||||
| 参数名 | 说明 | 必填 | 类型 | 默认值 | 备注 |
|
||||
| ------ | ---- | ---- | ---- | ------ | ---- |
|
||||
| | | | | | |
|
||||
3
packages/editor-framework/es/context.d.ts
vendored
3
packages/editor-framework/es/context.d.ts
vendored
@ -1,3 +0,0 @@
|
||||
/// <reference types="react" />
|
||||
declare const context: import("react").Context<{}>;
|
||||
export default context;
|
||||
@ -1,3 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
var context = createContext({});
|
||||
export default context;
|
||||
@ -1,30 +0,0 @@
|
||||
import { PureComponent } from 'react';
|
||||
import './index.scss';
|
||||
export default class LeftAddon extends PureComponent {
|
||||
static displayName: string;
|
||||
static propTypes: {
|
||||
active: any;
|
||||
config: any;
|
||||
disabled: any;
|
||||
dotted: any;
|
||||
locked: any;
|
||||
onClick: any;
|
||||
};
|
||||
static defaultProps: {
|
||||
active: boolean;
|
||||
config: {};
|
||||
disabled: boolean;
|
||||
dotted: boolean;
|
||||
locked: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
static contextType: any;
|
||||
constructor(props: any, context: any);
|
||||
componentDidMount(): void;
|
||||
componentWillUnmount(): void;
|
||||
handleClose: () => void;
|
||||
handleOpen: () => void;
|
||||
handleShow: () => void;
|
||||
renderIcon: (clickCallback: any) => JSX.Element;
|
||||
render(): JSX.Element;
|
||||
}
|
||||
@ -1,259 +0,0 @@
|
||||
import _extends from "@babel/runtime/helpers/extends";
|
||||
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import AppContext from '@ali/iceluna-sdk/lib/context/appContext';
|
||||
import { Balloon, Dialog, Icon, Badge } from '@alife/next';
|
||||
import './index.scss';
|
||||
|
||||
var LeftAddon = /*#__PURE__*/function (_PureComponent) {
|
||||
_inheritsLoose(LeftAddon, _PureComponent);
|
||||
|
||||
function LeftAddon(_props, context) {
|
||||
var _this;
|
||||
|
||||
_this = _PureComponent.call(this, _props, context) || this;
|
||||
|
||||
_this.handleClose = function () {
|
||||
var addonKey = _this.props.config && _this.props.config.addonKey;
|
||||
var currentAddon = _this.appHelper.addons && _this.appHelper.addons[addonKey];
|
||||
|
||||
if (currentAddon) {
|
||||
_this.utils.transformToPromise(currentAddon.close()).then(function () {
|
||||
_this.setState({
|
||||
dialogVisible: false
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_this.handleOpen = function () {
|
||||
// todo 对话框类型的插件初始时拿不到插件实例
|
||||
_this.setState({
|
||||
dialogVisible: true
|
||||
});
|
||||
};
|
||||
|
||||
_this.handleShow = function () {
|
||||
var _this$props = _this.props,
|
||||
disabled = _this$props.disabled,
|
||||
config = _this$props.config,
|
||||
onClick = _this$props.onClick;
|
||||
var addonKey = config && config.addonKey;
|
||||
if (disabled || !addonKey) return; //考虑到弹窗情况,延时发送消息
|
||||
|
||||
setTimeout(function () {
|
||||
return _this.appHelper.emit(addonKey + ".addon.activate");
|
||||
}, 0);
|
||||
|
||||
_this.handleOpen();
|
||||
|
||||
onClick && onClick();
|
||||
};
|
||||
|
||||
_this.renderIcon = function (clickCallback) {
|
||||
var _this$props2 = _this.props,
|
||||
active = _this$props2.active,
|
||||
disabled = _this$props2.disabled,
|
||||
dotted = _this$props2.dotted,
|
||||
locked = _this$props2.locked,
|
||||
_onClick = _this$props2.onClick,
|
||||
config = _this$props2.config;
|
||||
|
||||
var _ref = config || {},
|
||||
addonKey = _ref.addonKey,
|
||||
props = _ref.props;
|
||||
|
||||
var _ref2 = props || {},
|
||||
icon = _ref2.icon,
|
||||
title = _ref2.title;
|
||||
|
||||
return React.createElement("div", {
|
||||
className: classNames('luna-left-addon', addonKey, {
|
||||
active: active,
|
||||
disabled: disabled,
|
||||
locked: locked
|
||||
}),
|
||||
"data-tooltip": title,
|
||||
onClick: function onClick() {
|
||||
if (disabled) return; //考虑到弹窗情况,延时发送消息
|
||||
|
||||
clickCallback && clickCallback();
|
||||
_onClick && _onClick();
|
||||
}
|
||||
}, dotted ? React.createElement(Badge, {
|
||||
dot: true
|
||||
}, React.createElement(Icon, {
|
||||
type: icon,
|
||||
size: "small"
|
||||
})) : React.createElement(Icon, {
|
||||
type: icon,
|
||||
size: "small"
|
||||
}));
|
||||
};
|
||||
|
||||
_this.state = {
|
||||
dialogVisible: false
|
||||
};
|
||||
_this.appHelper = context.appHelper;
|
||||
_this.utils = _this.appHelper.utils;
|
||||
_this.constants = _this.appHelper.constants;
|
||||
return _this;
|
||||
}
|
||||
|
||||
var _proto = LeftAddon.prototype;
|
||||
|
||||
_proto.componentDidMount = function componentDidMount() {
|
||||
var config = this.props.config;
|
||||
var addonKey = config && config.addonKey;
|
||||
var appHelper = this.appHelper;
|
||||
|
||||
if (appHelper && addonKey) {
|
||||
appHelper.on(addonKey + ".dialog.show", this.handleShow);
|
||||
appHelper.on(addonKey + ".dialog.close", this.handleClose);
|
||||
}
|
||||
};
|
||||
|
||||
_proto.componentWillUnmount = function componentWillUnmount() {
|
||||
var config = this.props.config;
|
||||
var appHelper = this.appHelper;
|
||||
var addonKey = config && config.addonKey;
|
||||
|
||||
if (appHelper && addonKey) {
|
||||
appHelper.off(addonKey + ".dialog.show", this.handleShow);
|
||||
appHelper.off(addonKey + ".dialog.close", this.handleClose);
|
||||
}
|
||||
};
|
||||
|
||||
_proto.render = function render() {
|
||||
var _this2 = this;
|
||||
|
||||
var _this$props3 = this.props,
|
||||
dotted = _this$props3.dotted,
|
||||
locked = _this$props3.locked,
|
||||
active = _this$props3.active,
|
||||
disabled = _this$props3.disabled,
|
||||
config = _this$props3.config;
|
||||
|
||||
var _ref3 = config || {},
|
||||
addonKey = _ref3.addonKey,
|
||||
props = _ref3.props,
|
||||
type = _ref3.type,
|
||||
addonProps = _ref3.addonProps;
|
||||
|
||||
var _ref4 = props || {},
|
||||
_onClick2 = _ref4.onClick,
|
||||
title = _ref4.title;
|
||||
|
||||
var dialogVisible = this.state.dialogVisible;
|
||||
var _this$context = this.context,
|
||||
appHelper = _this$context.appHelper,
|
||||
components = _this$context.components;
|
||||
if (!addonKey || !type || !props) return null;
|
||||
var componentName = appHelper.utils.generateAddonCompName(addonKey);
|
||||
var localeProps = {};
|
||||
var locale = appHelper.locale,
|
||||
messages = appHelper.messages;
|
||||
|
||||
if (locale) {
|
||||
localeProps.locale = locale;
|
||||
}
|
||||
|
||||
if (messages && messages[componentName]) {
|
||||
localeProps.messages = messages[componentName];
|
||||
}
|
||||
|
||||
var AddonComp = components && components[componentName];
|
||||
var node = AddonComp && React.createElement(AddonComp, _extends({
|
||||
active: active,
|
||||
locked: locked,
|
||||
disabled: disabled,
|
||||
config: config,
|
||||
onClick: function onClick() {
|
||||
_onClick2 && _onClick2.call(null, appHelper);
|
||||
}
|
||||
}, localeProps, addonProps || {})) || null;
|
||||
|
||||
switch (type) {
|
||||
case 'LinkIcon':
|
||||
return React.createElement("a", props.linkProps || {}, this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, appHelper);
|
||||
}));
|
||||
|
||||
case 'Icon':
|
||||
return this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, appHelper);
|
||||
});
|
||||
|
||||
case 'DialogIcon':
|
||||
return React.createElement(Fragment, null, this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, appHelper);
|
||||
|
||||
_this2.handleOpen();
|
||||
}), React.createElement(Dialog, _extends({
|
||||
onOk: function onOk() {
|
||||
appHelper.emit(addonKey + ".dialog.onOk");
|
||||
|
||||
_this2.handleClose();
|
||||
},
|
||||
onCancel: this.handleClose,
|
||||
onClose: this.handleClose,
|
||||
title: title
|
||||
}, props.dialogProps || {}, {
|
||||
visible: dialogVisible
|
||||
}), node));
|
||||
|
||||
case 'BalloonIcon':
|
||||
return React.createElement(Balloon, _extends({
|
||||
trigger: this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, appHelper);
|
||||
}),
|
||||
align: "r",
|
||||
triggerType: ['click', 'hover']
|
||||
}, props.balloonProps || {}), node);
|
||||
|
||||
case 'PanelIcon':
|
||||
return this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, appHelper);
|
||||
|
||||
_this2.handleOpen();
|
||||
});
|
||||
|
||||
case 'Custom':
|
||||
return dotted ? React.createElement(Badge, {
|
||||
dot: true
|
||||
}, node) : node;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return LeftAddon;
|
||||
}(PureComponent);
|
||||
|
||||
LeftAddon.displayName = 'LunaLeftAddon';
|
||||
LeftAddon.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
config: PropTypes.shape({
|
||||
addonKey: PropTypes.string,
|
||||
addonProps: PropTypes.object,
|
||||
props: PropTypes.object,
|
||||
type: PropTypes.oneOf(['DialogIcon', 'BalloonIcon', 'PanelIcon', 'LinkIcon', 'Icon', 'Custom'])
|
||||
}),
|
||||
disabled: PropTypes.bool,
|
||||
dotted: PropTypes.bool,
|
||||
locked: PropTypes.bool,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
LeftAddon.defaultProps = {
|
||||
active: false,
|
||||
config: {},
|
||||
disabled: false,
|
||||
dotted: false,
|
||||
locked: false,
|
||||
onClick: function onClick() {}
|
||||
};
|
||||
LeftAddon.contextType = AppContext;
|
||||
export { LeftAddon as default };
|
||||
@ -1,59 +0,0 @@
|
||||
.luna-left-addon {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
height: 36px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #777;
|
||||
&.collapse {
|
||||
height: 40px;
|
||||
color: #8c8c8c;
|
||||
border-bottom: 1px solid #bfbfbf;
|
||||
}
|
||||
&.locked {
|
||||
color: red !important;
|
||||
}
|
||||
&.active {
|
||||
color: #fff !important;
|
||||
background-color: $color-brand1-9 !important;
|
||||
&.disabled {
|
||||
color: #fff;
|
||||
background-color: $color-fill1-7;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: $color-text1-1;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $color-brand1-1;
|
||||
color: $color-brand1-6;
|
||||
&:before {
|
||||
content: attr(data-tooltip);
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
top: 5px;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
color: #fff;
|
||||
z-index: 100;
|
||||
}
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 40px;
|
||||
top: 15px;
|
||||
border: 5px solid transparent;
|
||||
border-right-color: rgba(0, 0, 0, 0.75);
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import { PureComponent } from 'react';
|
||||
import './index.scss';
|
||||
export default class TopIcon extends PureComponent {
|
||||
static displayName: string;
|
||||
static propTypes: {
|
||||
active: any;
|
||||
className: any;
|
||||
disabled: any;
|
||||
icon: any;
|
||||
id: any;
|
||||
locked: any;
|
||||
onClick: any;
|
||||
showTitle: any;
|
||||
style: any;
|
||||
title: any;
|
||||
};
|
||||
static defaultProps: {
|
||||
active: boolean;
|
||||
className: string;
|
||||
disabled: boolean;
|
||||
icon: string;
|
||||
id: string;
|
||||
locked: boolean;
|
||||
onClick: () => void;
|
||||
showTitle: boolean;
|
||||
style: {};
|
||||
title: string;
|
||||
};
|
||||
render(): JSX.Element;
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
import _Button from "@alifd/next/es/button";
|
||||
import _Icon from "@alifd/next/es/icon";
|
||||
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import './index.scss';
|
||||
|
||||
var TopIcon = /*#__PURE__*/function (_PureComponent) {
|
||||
_inheritsLoose(TopIcon, _PureComponent);
|
||||
|
||||
function TopIcon() {
|
||||
return _PureComponent.apply(this, arguments) || this;
|
||||
}
|
||||
|
||||
var _proto = TopIcon.prototype;
|
||||
|
||||
_proto.render = function render() {
|
||||
var _this$props = this.props,
|
||||
active = _this$props.active,
|
||||
disabled = _this$props.disabled,
|
||||
icon = _this$props.icon,
|
||||
locked = _this$props.locked,
|
||||
title = _this$props.title,
|
||||
className = _this$props.className,
|
||||
id = _this$props.id,
|
||||
style = _this$props.style,
|
||||
showTitle = _this$props.showTitle,
|
||||
onClick = _this$props.onClick;
|
||||
return React.createElement(_Button, {
|
||||
type: "normal",
|
||||
size: "large",
|
||||
text: true,
|
||||
className: classNames('lowcode-top-btn', className, {
|
||||
active: active,
|
||||
disabled: disabled,
|
||||
locked: locked
|
||||
}),
|
||||
id: id,
|
||||
style: style,
|
||||
onClick: disabled ? null : onClick
|
||||
}, React.createElement("div", null, React.createElement(_Icon, {
|
||||
size: "large",
|
||||
type: icon
|
||||
}), showTitle && React.createElement("span", null, title)));
|
||||
};
|
||||
|
||||
return TopIcon;
|
||||
}(PureComponent);
|
||||
|
||||
TopIcon.displayName = 'TopIcon';
|
||||
TopIcon.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
icon: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
locked: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
showTitle: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
title: PropTypes.string
|
||||
};
|
||||
TopIcon.defaultProps = {
|
||||
active: false,
|
||||
className: '',
|
||||
disabled: false,
|
||||
icon: '',
|
||||
id: '',
|
||||
locked: false,
|
||||
onClick: function onClick() {},
|
||||
showTitle: false,
|
||||
style: {},
|
||||
title: ''
|
||||
};
|
||||
export { TopIcon as default };
|
||||
@ -1,32 +0,0 @@
|
||||
.next-btn.next-large.lowcode-top-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
margin: 4px -2px;
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
color: #777;
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: $color-text1-1;
|
||||
}
|
||||
&.locked {
|
||||
color: red !important;
|
||||
}
|
||||
i.next-icon {
|
||||
&:before {
|
||||
font-size: 17px;
|
||||
}
|
||||
margin-right: 0;
|
||||
line-height: 18px;
|
||||
}
|
||||
span {
|
||||
display: block;
|
||||
margin: 0px -5px 0;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { PureComponent } from 'react';
|
||||
import './index.scss';
|
||||
export default class TopPlugin extends PureComponent {
|
||||
static displayName: string;
|
||||
static defaultProps: {
|
||||
active: boolean;
|
||||
config: {};
|
||||
disabled: boolean;
|
||||
dotted: boolean;
|
||||
locked: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
constructor(props: any, context: any);
|
||||
componentDidMount(): void;
|
||||
componentWillUnmount(): void;
|
||||
handleShow: () => void;
|
||||
handleClose: () => void;
|
||||
handleOpen: () => void;
|
||||
renderIcon: (clickCallback: any) => JSX.Element;
|
||||
render(): JSX.Element;
|
||||
}
|
||||
@ -1,213 +0,0 @@
|
||||
import _Balloon from "@alifd/next/es/balloon";
|
||||
import _Dialog from "@alifd/next/es/dialog";
|
||||
import _extends from "@babel/runtime/helpers/extends";
|
||||
import _Badge from "@alifd/next/es/badge";
|
||||
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import TopIcon from '../TopIcon';
|
||||
import './index.scss';
|
||||
|
||||
var TopPlugin = /*#__PURE__*/function (_PureComponent) {
|
||||
_inheritsLoose(TopPlugin, _PureComponent);
|
||||
|
||||
function TopPlugin(_props, context) {
|
||||
var _this;
|
||||
|
||||
_this = _PureComponent.call(this, _props, context) || this;
|
||||
|
||||
_this.handleShow = function () {
|
||||
var _this$props = _this.props,
|
||||
disabled = _this$props.disabled,
|
||||
config = _this$props.config,
|
||||
onClick = _this$props.onClick;
|
||||
var addonKey = config && config.addonKey;
|
||||
if (disabled || !addonKey) return; //考虑到弹窗情况,延时发送消息
|
||||
|
||||
setTimeout(function () {
|
||||
return _this.appHelper.emit(addonKey + ".addon.activate");
|
||||
}, 0);
|
||||
|
||||
_this.handleOpen();
|
||||
|
||||
onClick && onClick();
|
||||
};
|
||||
|
||||
_this.handleClose = function () {
|
||||
var addonKey = _this.props.config && _this.props.config.addonKey;
|
||||
var currentAddon = _this.appHelper.addons && _this.appHelper.addons[addonKey];
|
||||
|
||||
if (currentAddon) {
|
||||
_this.utils.transformToPromise(currentAddon.close()).then(function () {
|
||||
_this.setState({
|
||||
dialogVisible: false
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_this.handleOpen = function () {
|
||||
// todo dialog类型的插件初始时拿不动插件实例
|
||||
_this.setState({
|
||||
dialogVisible: true
|
||||
});
|
||||
};
|
||||
|
||||
_this.renderIcon = function (clickCallback) {
|
||||
var _this$props2 = _this.props,
|
||||
active = _this$props2.active,
|
||||
disabled = _this$props2.disabled,
|
||||
dotted = _this$props2.dotted,
|
||||
locked = _this$props2.locked,
|
||||
config = _this$props2.config,
|
||||
_onClick = _this$props2.onClick;
|
||||
|
||||
var _ref = config || {},
|
||||
pluginKey = _ref.pluginKey,
|
||||
props = _ref.props;
|
||||
|
||||
var _ref2 = props || {},
|
||||
icon = _ref2.icon,
|
||||
title = _ref2.title;
|
||||
|
||||
var node = React.createElement(TopIcon, {
|
||||
className: "lowcode-top-addon " + pluginKey,
|
||||
active: active,
|
||||
disabled: disabled,
|
||||
locked: locked,
|
||||
icon: icon,
|
||||
title: title,
|
||||
onClick: function onClick() {
|
||||
if (disabled) return; //考虑到弹窗情况,延时发送消息
|
||||
|
||||
setTimeout(function () {
|
||||
return _this.appHelper.emit(pluginKey + ".addon.activate");
|
||||
}, 0);
|
||||
clickCallback && clickCallback();
|
||||
_onClick && _onClick();
|
||||
}
|
||||
});
|
||||
return dotted ? React.createElement(_Badge, {
|
||||
dot: true
|
||||
}, node) : node;
|
||||
};
|
||||
|
||||
_this.state = {
|
||||
dialogVisible: false
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
|
||||
var _proto = TopPlugin.prototype;
|
||||
|
||||
_proto.componentDidMount = function componentDidMount() {
|
||||
var config = this.props.config;
|
||||
var pluginKey = config && config.pluginKey; // const appHelper = this.appHelper;
|
||||
// if (appHelper && addonKey) {
|
||||
// appHelper.on(`${addonKey}.dialog.show`, this.handleShow);
|
||||
// appHelper.on(`${addonKey}.dialog.close`, this.handleClose);
|
||||
// }
|
||||
};
|
||||
|
||||
_proto.componentWillUnmount = function componentWillUnmount() {// const { config } = this.props;
|
||||
// const addonKey = config && config.addonKey;
|
||||
// const appHelper = this.appHelper;
|
||||
// if (appHelper && addonKey) {
|
||||
// appHelper.off(`${addonKey}.dialog.show`, this.handleShow);
|
||||
// appHelper.off(`${addonKey}.dialog.close`, this.handleClose);
|
||||
// }
|
||||
};
|
||||
|
||||
_proto.render = function render() {
|
||||
var _this2 = this;
|
||||
|
||||
var _this$props3 = this.props,
|
||||
active = _this$props3.active,
|
||||
dotted = _this$props3.dotted,
|
||||
locked = _this$props3.locked,
|
||||
disabled = _this$props3.disabled,
|
||||
config = _this$props3.config,
|
||||
editor = _this$props3.editor,
|
||||
Comp = _this$props3.pluginClass;
|
||||
|
||||
var _ref3 = config || {},
|
||||
pluginKey = _ref3.pluginKey,
|
||||
pluginProps = _ref3.pluginProps,
|
||||
props = _ref3.props,
|
||||
type = _ref3.type;
|
||||
|
||||
var _ref4 = props || {},
|
||||
_onClick2 = _ref4.onClick,
|
||||
title = _ref4.title;
|
||||
|
||||
var dialogVisible = this.state.dialogVisible;
|
||||
if (!pluginKey || !type || !Comp) return null;
|
||||
var node = React.createElement(Comp, _extends({
|
||||
active: active,
|
||||
locked: locked,
|
||||
disabled: disabled,
|
||||
config: config,
|
||||
onClick: function onClick() {
|
||||
_onClick2 && _onClick2.call(null, editor);
|
||||
}
|
||||
}, pluginProps));
|
||||
|
||||
switch (type) {
|
||||
case 'LinkIcon':
|
||||
return React.createElement("a", props.linkProps, this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, editor);
|
||||
}));
|
||||
|
||||
case 'Icon':
|
||||
return this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, editor);
|
||||
});
|
||||
|
||||
case 'DialogIcon':
|
||||
return React.createElement(Fragment, null, this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, editor);
|
||||
|
||||
_this2.handleOpen();
|
||||
}), React.createElement(_Dialog, _extends({
|
||||
onOk: function onOk() {
|
||||
editor.emit(pluginKey + ".dialog.onOk");
|
||||
|
||||
_this2.handleClose();
|
||||
},
|
||||
onCancel: this.handleClose,
|
||||
onClose: this.handleClose,
|
||||
title: title
|
||||
}, props.dialogProps, {
|
||||
visible: dialogVisible
|
||||
}), node));
|
||||
|
||||
case 'BalloonIcon':
|
||||
return React.createElement(_Balloon, _extends({
|
||||
trigger: this.renderIcon(function () {
|
||||
_onClick2 && _onClick2.call(null, editor);
|
||||
}),
|
||||
triggerType: ['click', 'hover']
|
||||
}, props.balloonProps), node);
|
||||
|
||||
case 'Custom':
|
||||
return dotted ? React.createElement(_Badge, {
|
||||
dot: true
|
||||
}, node) : node;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return TopPlugin;
|
||||
}(PureComponent);
|
||||
|
||||
TopPlugin.displayName = 'lowcodeTopPlugin';
|
||||
TopPlugin.defaultProps = {
|
||||
active: false,
|
||||
config: {},
|
||||
disabled: false,
|
||||
dotted: false,
|
||||
locked: false,
|
||||
onClick: function onClick() {}
|
||||
};
|
||||
export { TopPlugin as default };
|
||||
@ -1,2 +0,0 @@
|
||||
.lowcode-top-addon {
|
||||
}
|
||||
14
packages/editor-skeleton/es/config/skeleton.d.ts
vendored
14
packages/editor-skeleton/es/config/skeleton.d.ts
vendored
@ -1,14 +0,0 @@
|
||||
declare const routerConfig: {
|
||||
path: string;
|
||||
component: any;
|
||||
children: ({
|
||||
path: string;
|
||||
component: any;
|
||||
redirect?: undefined;
|
||||
} | {
|
||||
path: string;
|
||||
redirect: string;
|
||||
component?: undefined;
|
||||
})[];
|
||||
}[];
|
||||
export default routerConfig;
|
||||
@ -1,14 +0,0 @@
|
||||
import Dashboard from '@/pages/Dashboard';
|
||||
import BasicLayout from '@/layouts/BasicLayout';
|
||||
var routerConfig = [{
|
||||
path: '/',
|
||||
component: BasicLayout,
|
||||
children: [{
|
||||
path: '/dashboard',
|
||||
component: Dashboard
|
||||
}, {
|
||||
path: '/',
|
||||
redirect: '/dashboard'
|
||||
}]
|
||||
}];
|
||||
export default routerConfig;
|
||||
@ -1,2 +0,0 @@
|
||||
declare const asideMenuConfig: any[];
|
||||
export { asideMenuConfig };
|
||||
@ -1,3 +0,0 @@
|
||||
// 菜单配置
|
||||
var asideMenuConfig = [];
|
||||
export { asideMenuConfig };
|
||||
@ -1,33 +0,0 @@
|
||||
body {
|
||||
font-family: PingFangSC-Regular, Roboto, Helvetica Neue, Helvetica, Tahoma,
|
||||
Arial, PingFang SC-Light, Microsoft YaHei;
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.next-loading {
|
||||
.next-loading-wrap {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.lowcode-editor {
|
||||
.lowcode-main-content {
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
background-color: #d8d8d8;
|
||||
}
|
||||
.lowcode-center-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
8
packages/editor-skeleton/es/index.d.ts
vendored
8
packages/editor-skeleton/es/index.d.ts
vendored
@ -1,8 +0,0 @@
|
||||
import { PureComponent } from 'react';
|
||||
import './global.scss';
|
||||
export default class Skeleton extends PureComponent {
|
||||
static displayName: string;
|
||||
constructor(props: any);
|
||||
componentWillUnmount(): void;
|
||||
render(): JSX.Element;
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
import _ConfigProvider from "@alifd/next/es/config-provider";
|
||||
import _Loading from "@alifd/next/es/loading";
|
||||
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||
import React, { PureComponent } from 'react'; // import Editor from '@ali/lowcode-engine-editor';
|
||||
|
||||
import TopArea from './layouts/TopArea';
|
||||
import LeftArea from './layouts/LeftArea';
|
||||
import CenterArea from './layouts/CenterArea';
|
||||
import RightArea from './layouts/RightArea';
|
||||
import './global.scss';
|
||||
|
||||
var Skeleton = /*#__PURE__*/function (_PureComponent) {
|
||||
_inheritsLoose(Skeleton, _PureComponent);
|
||||
|
||||
function Skeleton(props) {
|
||||
var _this;
|
||||
|
||||
_this = _PureComponent.call(this, props) || this; // this.editor = new Editor(props.config, props.utils);
|
||||
|
||||
_this.editor = {
|
||||
on: function on() {},
|
||||
off: function off() {},
|
||||
config: props.config,
|
||||
pluginComponents: props.pluginComponents
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
|
||||
var _proto = Skeleton.prototype;
|
||||
|
||||
_proto.componentWillUnmount = function componentWillUnmount() {// this.editor && this.editor.destroy();
|
||||
// this.editor = null;
|
||||
};
|
||||
|
||||
_proto.render = function render() {
|
||||
var _this$props = this.props,
|
||||
location = _this$props.location,
|
||||
history = _this$props.history,
|
||||
messages = _this$props.messages;
|
||||
this.editor.location = location;
|
||||
this.editor.history = history;
|
||||
this.editor.messages = messages;
|
||||
return React.createElement(_ConfigProvider, null, React.createElement(_Loading, {
|
||||
tip: "Loading",
|
||||
size: "large",
|
||||
visible: false,
|
||||
shape: "fusion-reactor",
|
||||
fullScreen: true
|
||||
}, React.createElement("div", {
|
||||
className: "lowcode-editor"
|
||||
}, React.createElement(TopArea, {
|
||||
editor: this.editor
|
||||
}), React.createElement("div", {
|
||||
className: "lowcode-main-content"
|
||||
}, React.createElement(LeftArea.Nav, {
|
||||
editor: this.editor
|
||||
}), React.createElement(LeftArea.Panel, {
|
||||
editor: this.editor
|
||||
}), React.createElement(CenterArea, {
|
||||
editor: this.editor
|
||||
}), React.createElement(RightArea, {
|
||||
editor: this.editor
|
||||
})))));
|
||||
};
|
||||
|
||||
return Skeleton;
|
||||
}(PureComponent);
|
||||
|
||||
Skeleton.displayName = 'lowcodeEditorSkeleton';
|
||||
export { Skeleton as default };
|
||||
@ -1,7 +0,0 @@
|
||||
import { PureComponent } from 'react';
|
||||
import './index.scss';
|
||||
export default class CenterArea extends PureComponent {
|
||||
static displayName: string;
|
||||
constructor(props: any);
|
||||
render(): JSX.Element;
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||
import React, { PureComponent } from 'react';
|
||||
import './index.scss';
|
||||
|
||||
var CenterArea = /*#__PURE__*/function (_PureComponent) {
|
||||
_inheritsLoose(CenterArea, _PureComponent);
|
||||
|
||||
function CenterArea(props) {
|
||||
return _PureComponent.call(this, props) || this;
|
||||
}
|
||||
|
||||
var _proto = CenterArea.prototype;
|
||||
|
||||
_proto.render = function render() {
|
||||
return React.createElement("div", {
|
||||
className: "lowcode-center-area"
|
||||
});
|
||||
};
|
||||
|
||||
return CenterArea;
|
||||
}(PureComponent);
|
||||
|
||||
CenterArea.displayName = 'lowcodeCenterArea';
|
||||
export { CenterArea as default };
|
||||
@ -1,3 +0,0 @@
|
||||
.lowcode-center-area {
|
||||
padding: 12px;
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
declare const _default: {
|
||||
Nav: any;
|
||||
Panel: any;
|
||||
};
|
||||
export default _default;
|
||||
@ -1,6 +0,0 @@
|
||||
import Nav from './nav';
|
||||
import Panel from './panel';
|
||||
export default {
|
||||
Nav: Nav,
|
||||
Panel: Panel
|
||||
};
|
||||
@ -1,21 +0,0 @@
|
||||
.lowcode-left-area-nav {
|
||||
width: 48px;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
border-right: 1px solid #e8ebee;
|
||||
position: relative;
|
||||
.top-area {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
max-height: 100%;
|
||||
}
|
||||
.bottom-area {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
max-height: calc(100% - 20px);
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import { PureComponent } from 'react';
|
||||
import './index.scss';
|
||||
export default class LeftAreaPanel extends PureComponent {
|
||||
static displayName: string;
|
||||
constructor(props: any);
|
||||
render(): JSX.Element;
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
|
||||
import React, { PureComponent } from 'react';
|
||||
import './index.scss';
|
||||
|
||||
var LeftAreaPanel = /*#__PURE__*/function (_PureComponent) {
|
||||
_inheritsLoose(LeftAreaPanel, _PureComponent);
|
||||
|
||||
function LeftAreaPanel(props) {
|
||||
return _PureComponent.call(this, props) || this;
|
||||
}
|
||||
|
||||
var _proto = LeftAreaPanel.prototype;
|
||||
|
||||
_proto.render = function render() {
|
||||
return React.createElement("div", {
|
||||
className: "lowcode-left-area-nav"
|
||||
});
|
||||
};
|
||||
|
||||
return LeftAreaPanel;
|
||||
}(PureComponent);
|
||||
|
||||
LeftAreaPanel.displayName = 'lowcodeLeftAreaNav';
|
||||
export { LeftAreaPanel as default };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user