diff --git a/packages/code-generator/src/demo/loopDemo.ts b/packages/code-generator/src/demo/loopDemo.ts deleted file mode 100644 index cf5b83bc5..000000000 --- a/packages/code-generator/src/demo/loopDemo.ts +++ /dev/null @@ -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组件,在右侧“数据”面板,设置循环数据;
\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: '小二:外包:商家 1:1:1', - }, - ], - }, - { - 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; diff --git a/packages/code-generator/src/demo/main.ts b/packages/code-generator/src/demo/main.ts index 722cff307..76fa8a837 100644 --- a/packages/code-generator/src/demo/main.ts +++ b/packages/code-generator/src/demo/main.ts @@ -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 ==========`); diff --git a/packages/code-generator/src/demo/simpleDemo.ts b/packages/code-generator/src/demo/simpleDemo.ts new file mode 100644 index 000000000..6e45cba6a --- /dev/null +++ b/packages/code-generator/src/demo/simpleDemo.ts @@ -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; diff --git a/packages/code-generator/src/parse/SchemaParser.ts b/packages/code-generator/src/parse/SchemaParser.ts index 06c2e0548..b1880e49e 100644 --- a/packages/code-generator/src/parse/SchemaParser.ts +++ b/packages/code-generator/src/parse/SchemaParser.ts @@ -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(children, { + node: (i: IComponentNodeItem) => [i.componentName], + }); } } diff --git a/packages/code-generator/src/plugins/component/react/jsx.ts b/packages/code-generator/src/plugins/component/react/jsx.ts index ff2a3f8cf..7dad9c0b3 100644 --- a/packages/code-generator/src/plugins/component/react/jsx.ts +++ b/packages/code-generator/src/plugins/component/react/jsx.ts @@ -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(``); } else { @@ -87,6 +89,15 @@ function generateNode(nodeItem: IComponentNodeItem): string { return codePieces.join(' '); } +function generateChildren(children: ChildNodeType): string[] { + return handleChildren(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 = `(${ir.children - .map(child => generateNode(child)) - .join('')})`; + const childrenCode = generateChildren(ir.children); + if (childrenCode.length === 1) { + jsxContent = `(${childrenCode[0]})`; + } else { + jsxContent = `(${childrenCode.join( + '', + )})`; + } } next.chunks.push({ diff --git a/packages/code-generator/src/types/intermediate.ts b/packages/code-generator/src/types/intermediate.ts index 57d696b82..7ac66079a 100644 --- a/packages/code-generator/src/types/intermediate.ts +++ b/packages/code-generator/src/types/intermediate.ts @@ -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; - dependencies?: Record; - [key: string]: unknown; -} - export interface IParseResult { containers: IContainerInfo[]; globalUtils?: IUtilInfo; diff --git a/packages/code-generator/src/types/schema.ts b/packages/code-generator/src/types/schema.ts index 071e94c81..22b7ab870 100644 --- a/packages/code-generator/src/types/schema.ts +++ b/packages/code-generator/src/types/schema.ts @@ -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 { * 返回值:数据对象data,将会在渲染引擎和schemaToCode中通过调用this.setState(...)将返回的数据对象生效到state中; * 支持返回一个Promise,通过resolve(返回数据),常用于串型发送请求场景,配合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; diff --git a/packages/code-generator/src/utils/children.ts b/packages/code-generator/src/utils/children.ts new file mode 100644 index 000000000..3d848f988 --- /dev/null +++ b/packages/code-generator/src/utils/children.ts @@ -0,0 +1,35 @@ +import { + ChildNodeItem, + ChildNodeType, + IComponentNodeItem, + IJSExpression, +} from '@/types'; + +// tslint:disable-next-line: no-empty +const noop = () => []; + +export function handleChildren( + 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); + } +}