feat: demo schema & complex children type

This commit is contained in:
春希 2020-03-17 16:33:02 +08:00
parent 0210c4d9ba
commit a5ee6bd558
8 changed files with 309 additions and 444 deletions

View File

@ -1,276 +0,0 @@
import { IBasicSchema } from '@/types';
const demoData: IBasicSchema = {
version: '1.0.0',
componentsMap: [
{
componentName: 'Button',
package: '@alifd/next',
version: '1.19.4',
destructuring: true,
exportName: 'Select',
subName: 'Button',
},
],
utils: [
{
name: 'clone',
type: 'npm',
content: {
package: 'lodash',
version: '0.0.1',
exportName: 'clone',
subName: '',
destructuring: false,
main: '/lib/clone',
},
},
{
name: 'moment',
type: 'npm',
content: {
package: '@alife/next',
version: '0.0.1',
exportName: 'Moment',
subName: '',
destructuring: true,
main: '',
},
},
],
componentsTree: [
{
componentName: 'Page',
fileName: 'loopDemo',
props: {},
children: [
{
componentName: 'Html',
props: {
html:
'1.选中Col组件在右侧“数据”面板设置循环数据<br/>\n2.给Col组件内的子组件文本内容绑定对应的数据变量this.item获取当前循环数据this.index获取当前循环序号',
},
},
{
componentName: 'Row',
props: {
style: {
paddingTop: 30,
paddingRight: 30,
paddingBottom: 30,
paddingLeft: 30,
},
},
children: [
{
componentName: 'Col',
props: {},
children: [
{
componentName: 'Text',
props: {
style: {
display: 'block',
marginBottom: 8,
fontWeight: 'bold',
fontSize: 14,
lineHeight: '32px',
},
text: {
type: 'JSExpression',
value: 'this.item.title',
},
},
},
{
componentName: 'Text',
props: {
style: {
display: 'block',
marginBottom: 12,
fontWeight: 'bold',
fontSize: 16,
color: '#65aa14',
lineHeight: '12px',
},
text: {
type: 'JSExpression',
value: 'this.item.num',
},
},
},
{
componentName: 'Text',
props: {
style: {
display: 'block',
color: '#9b9b9b',
},
text: {
type: 'JSExpression',
value: 'this.item.description',
},
},
},
],
loop: [
{
title: '活跃UV',
num: 2783,
description: '小二外包商家12',
},
{
title: '活跃PV',
num: 17382,
description: '小二外包商家123',
},
{
title: '不活跃页面数',
num: 36,
description: '占总页面数比例 30%',
},
{
title: '人均使用时长',
num: 788,
description: '人均使用频次',
},
{
title: '新增用户数',
num: 14,
description: '小二:外包:商家 111',
},
],
},
{
componentName: 'Col',
props: {},
children: [
{
componentName: 'Text',
props: {
style: {
display: 'block',
marginBottom: 8,
fontWeight: 'bold',
fontSize: '14px',
lineHeight: '32px',
},
text: '更多用户数据分析',
},
},
{
componentName: 'Button',
props: {
type: 'primary',
style: {
margin: '0 5px 0 5px',
},
},
children: '查看详情',
},
],
},
],
},
{
componentName: 'Table',
props: {
hasBorder: true,
hasHeader: true,
dataSource: [
{
id: 1,
name: 'a1',
age: 1,
},
{
id: 2,
name: 'a2',
age: 2,
},
{
id: 3,
name: 'a3',
age: 3,
},
{
id: 4,
name: 'a4',
age: 4,
},
],
},
children: [
{
componentName: 'TableColumn',
props: {
title: {
type: 'JSExpression',
value: 'this.item.title',
},
dataIndex: {
type: 'JSExpression',
value: 'this.item.dataIndex',
},
},
loop: {
type: 'JSExpression',
value: 'this.state.columns',
},
},
],
},
],
state: {
dataSource: [
{
id: 1,
name: 'a1',
age: 21,
},
{
id: 2,
name: 'a2',
age: 22,
},
{
id: 3,
name: 'a3',
age: 23,
},
{
id: 4,
name: 'a4',
age: 24,
},
],
columns: [
{
title: 'ID',
dataIndex: 'id',
},
{
title: '姓名',
dataIndex: 'name',
},
{
title: '年龄',
dataIndex: 'age',
},
],
},
},
],
i18n: {
'zh-CN': {
'i18n-jwg27yo4': '你好',
'i18n-jwg27yo3': '中国',
},
'en-US': {
'i18n-jwg27yo4': 'Hello',
'i18n-jwg27yo3': 'China',
},
},
};
export default demoData;

View File

@ -1,136 +1,7 @@
import { IProjectSchema, IResultDir, IResultFile } from '@/types';
import { IResultDir, IResultFile } from '@/types';
import CodeGenerator from '@/index';
const schema: IProjectSchema = {
version: '1.0.0',
componentsMap: [
{
componentName: 'Button',
package: 'alife/next',
version: '1.0.0',
destructuring: true,
exportName: 'Select',
subName: 'Button',
},
],
componentsTree: [
{
componentName: 'Page',
fileName: 'Page1',
props: {},
css: 'body {font-size: 12px;} .table { width: 100px;}',
children: [
{
componentName: 'Div',
props: {
className: '',
},
children: [
{
componentName: 'Button',
props: {
prop1: 1234,
prop2: [
{
label: '选项1',
value: 1,
},
{
label: '选项2',
value: 2,
},
],
prop3: [
{
name: 'myName',
rule: {
type: 'JSExpression',
value: '/w+/i',
},
},
],
valueBind: {
type: 'JSExpression',
value: 'this.state.user.name',
},
onClick: {
type: 'JSExpression',
value: 'function(e) { console.log(e.target.innerText) }',
},
onClick2: {
type: 'JSExpression',
value: 'this.submit',
},
},
},
],
},
],
},
],
utils: [
{
name: 'clone',
type: 'npm',
content: {
package: 'lodash',
version: '0.0.1',
exportName: 'clone',
subName: '',
destructuring: false,
main: '/lib/clone',
},
},
{
name: 'beforeRequestHandler',
type: 'function',
content: {
type: 'JSExpression',
value: 'function(){\n ... \n}',
},
},
],
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',
},
},
meta: {
name: 'demo应用',
git_group: 'appGroup',
project_name: 'app_demo',
description: '这是一个测试应用',
spma: 'spa23d',
creator: '月飞',
},
i18n: {
'zh-CN': {
i18nJwg27yo4: '你好',
i18nJwg27yo3: '中国',
},
'en-US': {
i18nJwg27yo4: 'Hello',
i18nJwg27yo3: 'China',
},
},
};
import demoSchema from './simpleDemo';
function flatFiles(rootName: string | null, dir: IResultDir): IResultFile[] {
const dirRoot: string = rootName ? `${rootName}/${dir.name}` : dir.name;
@ -148,7 +19,7 @@ function flatFiles(rootName: string | null, dir: IResultDir): IResultFile[] {
function main() {
const createIceJsProjectBuilder = CodeGenerator.solutions.icejs;
const builder = createIceJsProjectBuilder();
builder.generateProject(schema).then(result => {
builder.generateProject(demoSchema).then(result => {
const files = flatFiles('.', result);
files.forEach(file => {
console.log(`========== ${file.name} Start ==========`);

View File

@ -0,0 +1,230 @@
import { IProjectSchema } from '@/types';
const demoData: IProjectSchema = {
version: '1.0.0',
componentsMap: [
{
componentName: 'Button',
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'Button',
},
{
componentName: 'Button.Group',
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'Button',
subName: 'Group',
},
{
componentName: 'Input',
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'Input',
},
{
componentName: 'Form',
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'Form',
},
{
componentName: 'Form.Item',
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'Form',
subName: 'Item',
},
{
componentName: 'NumberPicker',
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'NumberPicker',
},
{
componentName: 'Select',
package: '@alifd/next',
version: '1.19.18',
destructuring: true,
exportName: 'Select',
},
],
componentsTree: [
{
componentName: 'Page',
id: 'node$1',
props: {
ref: 'outterView',
autoLoading: true,
},
fileName: 'test',
state: {
text: 'outter',
},
children: [
{
componentName: 'Form',
id: 'node$2',
props: {
labelCol: 4,
style: {},
ref: 'testForm',
},
children: [
{
componentName: 'Form.Item',
id: 'node$3',
props: {
label: '姓名:',
name: 'name',
initValue: '李雷',
},
children: [
{
componentName: 'Input',
id: 'node$4',
props: {
placeholder: '请输入',
size: 'medium',
style: {
width: 320,
},
},
},
],
},
{
componentName: 'Form.Item',
id: 'node$5',
props: {
label: '年龄:',
name: 'age',
initValue: '22',
},
children: [
{
componentName: 'NumberPicker',
id: 'node$6',
props: {
size: 'medium',
type: 'normal',
},
},
],
},
{
componentName: 'Form.Item',
id: 'node$7',
props: {
label: '职业:',
name: 'profession',
},
children: [
{
componentName: 'Select',
id: 'node$8',
props: {
dataSource: [
{
label: '教师',
value: 't',
},
{
label: '医生',
value: 'd',
},
{
label: '歌手',
value: 's',
},
],
},
},
],
},
{
componentName: 'Div',
id: 'node$9',
props: {
style: {
textAlign: 'center',
},
},
children: [
{
componentName: 'Button.Group',
id: 'node$a',
props: {},
children: [
{
componentName: 'Button',
id: 'node$b',
props: {
type: 'primary',
style: {
margin: '0 5px 0 5px',
},
htmlType: 'submit',
},
children: ['提交'],
},
{
componentName: 'Button',
id: 'node$d',
props: {
type: 'normal',
style: {
margin: '0 5px 0 5px',
},
htmlType: 'reset',
},
children: ['重置'],
},
],
},
],
},
],
},
],
},
],
constants: {
ENV: 'prod',
DOMAIN: 'xxx.alibaba-inc.com',
},
css: 'body {font-size: 12px;} .table { width: 100px;}',
config: {
sdkVersion: '1.0.3',
historyMode: 'hash',
targetRootID: 'J_Container',
layout: {
componentName: 'BasicLayout',
props: {
logo: '...',
name: '测试网站',
},
},
theme: {
package: '@alife/theme-fusion',
version: '^0.1.0',
primary: '#ff9966',
},
},
meta: {
name: 'demo应用',
git_group: 'appGroup',
project_name: 'app_demo',
description: '这是一个测试应用',
spma: 'spa23d',
creator: '月飞',
},
};
export default demoData;

View File

@ -5,9 +5,12 @@
import { SUPPORT_SCHEMA_VERSION_LIST } from '../const';
import { handleChildren } from '../utils/children';
import { uniqueArray } from '../utils/common';
import {
ChildNodeItem,
ChildNodeType,
CodeGeneratorError,
CompatibilityError,
DependencyType,
@ -169,12 +172,10 @@ class SchemaParser implements ISchemaParser {
};
}
public getComponentNames(list: IComponentNodeItem[]): string[] {
const names = list.map(i => i.componentName);
const namesForward = list
.map(i => this.getComponentNames(i.children || []))
.reduce((p, c) => p.concat(c), []);
return names.concat(namesForward);
public getComponentNames(children: ChildNodeType): string[] {
return handleChildren<string>(children, {
node: (i: IComponentNodeItem) => [i.componentName],
});
}
}

View File

@ -1,9 +1,7 @@
import { REACT_CHUNK_NAME } from './const';
import { generateCompositeType } from '../../utils/compositeType';
import {
BuilderComponentPlugin,
ChildNodeItem,
ChildNodeType,
ChunkType,
FileType,
ICodeStruct,
@ -13,6 +11,10 @@ import {
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]);
@ -52,9 +54,9 @@ function generateNode(nodeItem: IComponentNodeItem): string {
);
codePieces.push.apply(codePieces, propLines);
if (nodeItem.children && nodeItem.children.length > 0) {
if (nodeItem.children && (nodeItem.children as unknown[]).length > 0) {
codePieces.push('>');
const childrenLines = nodeItem.children.map(child => generateNode(child));
const childrenLines = generateChildren(nodeItem.children);
codePieces.push.apply(codePieces, childrenLines);
codePieces.push(`</${nodeItem.componentName}>`);
} else {
@ -87,6 +89,15 @@ function generateNode(nodeItem: IComponentNodeItem): string {
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,
@ -95,14 +106,17 @@ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
const ir = next.ir as IContainerInfo;
let jsxContent: string;
if (!ir.children || ir.children.length === 0) {
if (!ir.children || (ir.children as unknown[]).length === 0) {
jsxContent = 'null';
} else if (ir.children.length === 1) {
jsxContent = `(${generateNode(ir.children[0])})`;
} else {
jsxContent = `(<React.Fragment>${ir.children
.map(child => generateNode(child))
.join('')}</React.Fragment>)`;
const childrenCode = generateChildren(ir.children);
if (childrenCode.length === 1) {
jsxContent = `(${childrenCode[0]})`;
} else {
jsxContent = `(<React.Fragment>${childrenCode.join(
'',
)}</React.Fragment>)`;
}
}
next.chunks.push({

View File

@ -8,18 +8,6 @@ import {
IUtilItem,
} from './index';
export interface IPackageJSON {
name: string;
description: string;
version: string;
main?: string;
author?: string;
license?: string;
scripts?: Record<string, string>;
dependencies?: Record<string, string>;
[key: string]: unknown;
}
export interface IParseResult {
containers: IContainerInfo[];
globalUtils?: IUtilInfo;

View File

@ -91,8 +91,8 @@ export interface IInlineStyle {
[cssAttribute: string]: string | number | IJSExpression;
}
type ChildNodeItem = string | IJSExpression | IComponentNodeItem;
type ChildNodeType = ChildNodeItem | ChildNodeItem[];
export type ChildNodeItem = string | IJSExpression | IComponentNodeItem;
export type ChildNodeType = ChildNodeItem | ChildNodeItem[];
/**
* -
@ -102,6 +102,8 @@ type ChildNodeType = ChildNodeItem | ChildNodeItem[];
* @interface IComponentNodeItem
*/
export interface IComponentNodeItem {
// TODO: 不需要 id 字段,暂时简单兼容
id?: string;
componentName: string; // 组件名称 必填、首字母大写
props: {
className?: string; // 组件样式类名
@ -170,7 +172,7 @@ export interface IDataSource {
* dataschemaToCode中通过调用this.setState(...)state中
* Promiseresolve()this.dataSourceMap[oneRequest.id].load()使
*/
dataHandler: IJSExpression;
dataHandler?: IJSExpression;
}
/**
@ -182,7 +184,7 @@ export interface IDataSource {
export interface IDataSourceConfig {
id: string; // 数据请求ID标识
isInit: boolean; // 是否为初始数据 支持表达式 值为true时将在组件初始化渲染时自动发送当前数据请求
type: 'fetch' | 'mtop' | 'jsonp' | 'custom'; // 数据请求类型
type: 'fetch' | 'mtop' | 'jsonp' | 'custom' | 'doServer'; // 数据请求类型
requestHandler?: IJSExpression; // 自定义扩展的外部请求处理器 仅type='custom'时生效
options?: IFetchOptions; // 请求参数配置 每种请求类型对应不同参数
dataHandler?: IJSExpression; // 数据结果处理函数,形如:(data, err) => Object
@ -201,8 +203,8 @@ export interface IFetchOptions {
[key: string]: any;
};
method: 'GET' | 'POST';
isCors: boolean; // 是否支持跨域对应credentials = 'include'
timeout: number; // 超时时长
isCors?: boolean; // 是否支持跨域对应credentials = 'include'
timeout?: number; // 超时时长
headers?: {
// 自定义请求头
[key: string]: string;

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