From f25feba63f181efa83f1a8dff530e1c39ab1b34c Mon Sep 17 00:00:00 2001 From: liujuping Date: Mon, 8 Aug 2022 17:30:37 +0800 Subject: [PATCH] feat: added lowcode engine standard specs --- specs/assets-spec.md | 687 +++++++++++++++ specs/lowcode-spec.md | 1462 ++++++++++++++++++++++++++++++++ specs/material-spec.md | 1821 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 3970 insertions(+) create mode 100644 specs/assets-spec.md create mode 100644 specs/lowcode-spec.md create mode 100644 specs/material-spec.md diff --git a/specs/assets-spec.md b/specs/assets-spec.md new file mode 100644 index 000000000..ab0e1abc0 --- /dev/null +++ b/specs/assets-spec.md @@ -0,0 +1,687 @@ +# 《低代码引擎资产包协议规范》 + +# 1 介绍 + +## 1.1 本协议规范涉及的问题域 + +- 定义本协议版本号规范 +- 定义本协议中每个子规范需要被支持的 Level +- 定义本协议相关的领域名词 +- 定义低代码资产包协议版本号规范(A) +- 定义低代码资产包协议组件及依赖资源描述规范(A) +- 定义低代码资产包协议组件描述资源加载规范(A) +- 定义低代码资产包协议组件在面板展示规范(AA) + +## 1.2 协议草案起草人 + +- 撰写:金禅、璿玑、彼洋 +- 审阅:力皓、絮黎、光弘、戊子、潕量、游鹿 + +## 1.3 版本号 + +1.1.0 + +## 1.4 协议版本号规范(A) + +本协议采用语义版本号,版本号格式为 `major.minor.patch` 的形式。 + +- major 是大版本号:用于发布不向下兼容的协议格式修改 +- minor 是小版本号:用于发布向下兼容的协议功能新增 +- patch 是补丁号:用于发布向下兼容的协议问题修正 + +## 1.5 协议中子规范 Level 定义 + +| 规范等级 | 实现要求 | +| -------- | ------------------------------------------------------------ | +| A | 基础规范,低代码引擎核心层支持; | +| AA | 推荐规范,由低代码引擎官方插件、setter 支持。 | +| AAA | 参考规范,需由基于引擎的上层搭建平台支持,实现可参考该规范。 | + +## 1.6 名词术语 + +- **资产包**: 低代码引擎加载资源的动态数据集合,主要包含组件及其依赖的资源、组件低代码描述、动态插件/设置器资源等。 + +## 1.7 背景 + +根据低代码引擎的实现,一个组件要在引擎上渲染和配置,需要提供组件的 umd 资源以及组件的`低代码描述`,并且组件通常都是以集合的形式被引擎消费的;除了组件之外,还有组件的依赖资源、引擎的动态插件/设置器等资源也需要注册到引擎中;因此我们定义了“低代码资产包”这个数据结构,来描述引擎所需加载的动态资源的集合。 + +## 1.8 受众 + +本协议适用于使用“低代码引擎”构建搭建平台的开发者,通过本协议的定义来进行资源的分类和加载。阅读及使用本协议,需要对低代码搭建平台的交互和实现有一定的了解,对前端开发相关技术栈的熟悉也会有帮助,协议中对通用的前端相关术语不会做进一步的解释说明。 + +# 2 协议结构 + +协议最顶层结构如下,包含 7 方面的描述内容: + +- version { String } 当前协议版本号 +- packages { Array } 低代码编辑器中加载的资源列表 +- components { Array } 所有组件的描述协议列表 +- sort { Object } 用于描述组件面板中的 tab 和 category +- plugins { Array } 设计器插件描述协议列表 +- setters { Array } 设计器中设置器描述协议列表 +- extConfig { Object } 平台自定义扩展字段 + +## 2.1 version(A) + +定义当前协议 schema 的版本号; + +| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 | +| ---------- | ------ | ---------- | -------- | ------ | +| version | String | 协议版本号 | - | 1.1.0 | + +## 2.2 packages(A) + +定义低代码编辑器中加载的资源列表,包含公共库和组件(库) cdn 资源等; + +| 字段 | 字段描述 | 字段类型 | 规范等级 | 备注 | +| -------------------- | --------------------------------------------------------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------- | +| packages[].id? | 资源唯一标识 | String | A | 资源唯一标识,如果为空,则以 package 为唯一标识 | +| packages[].title? | 资源标题 | String | A | 资源标题 | +| packages[].package | npm 包名 | String | A | 组件资源唯一标识 | +| packages[].version | npm 包版本号 | String | A | 组件资源版本号 | +| packages[].type | 资源包类型 | String | AA | 取值为: proCode(源码)、lowCode(低代码,默认为 proCode | +| packages[].schema | 低代码组件 schema 内容 | object | AA | 取值为: proCode(源码)、lowCode(低代码) | +| packages[].deps | 当前资源包的依赖资源的唯一标识列表 | Array | A | 唯一标识为 id 或者 package 对应的值 | +| packages[].library | 作为全局变量引用时的名称,用来定义全局变量名 | String | A | 低代码引擎通过该字段获取组件实例 | +| packages[].editUrls | 组件编辑态视图打包后的 CDN url 列表,包含 js 和 css | Array | A | 低代码引擎编辑器会加载这些 url | +| packages[].urls | 组件渲染态视图打包后的 CDN url 列表,包含 js 和 css | Array | AA | 低代码引擎渲染模块会加载这些 url | +| packages[].advancedEditUrls | 组件多个编辑态视图打包后的 CDN url 列表集合,包含 js 和 css | Object | AAA | 上层平台根据特定标识提取某个编辑态的资源,低代码引擎编辑器会加载这些资源,优先级高于 packages[].editUrls | +| packages[].advancedUrls | 组件多个端的渲染态视图打包后的 CDN url 列表集合,包含 js 和 css | Object | AAA | 上层平台根据特定标识提取某个渲染态的资源, 低代码引擎渲染模块会加载这些资源,优先级高于 packages[].urls | +| packages[].external | 当前资源在作为其他资源的依赖,在其他依赖打包时时是否被排除了(同 webpack 中 external 概念) | Boolean | AAA | 某些资源会被单独提取出来,是其他依赖的前置依赖,根据这个字段决定是否提前加载该资源 | +| packages[].loadEnv | 指定当前资源加载的环境 | Array | AAA | 主要用于指定 external 资源加载的环境,取值为 design(设计态)、runtime(预览态)中的一个或多个 | +| packages[].exportSourceId | 标识当前 package 内容是从哪个 package 导出来的 | String | AAA | 此时 urls 无效 | +| packages[].exportSourceLibrary | 标识当前 package 是从 window 上的哪个属性导出来的 | String | AAA | exportSourceId 的优先级高于exportSourceLibrary ,此时 urls 无效 | +| packages[].async | 标识当前 package 资源加载在 window.library 上的是否是一个异步对象 | Boolean | A | async 为 true 时,需要通过 await 才能拿到真正内容 | +| packages[].exportMode | 标识当前 package 从其他 package 的导出方式 | String | A | 目前只支持 `"functionCall"`, exportMode等于 `"functionCall"` 时,当前package 的内容以函数的方式从其他 package 中导出,具体导出接口如: (library: string, packageName: string, isRuntime?: boolean) => any | Promise, library 为当前 package 的 library, packageName 为当前的包名,返回值为当前 package 的导出内容 | + +描述举例: + +```json +{ + "packages": [ + { + "title": "fusion 组件库", + "package": "@alifd/next", + "version": "1.23.0", + "urls": [ + "https://g.alicdn.com/code/lib/alifd__next/1.23.18/next.min.css", + "https://g.alicdn.com/code/lib/alifd__next/1.23.18/next-with-locales.min.js" + ], + "library": "Next" + }, + { + "title": "Fusion 精品组件库", + "package": "@alife/fusion-ui", + "version": "0.1.5", + "editUrls": [ + "https://g.alicdn.com/code/npm/@alife/fusion-ui/0.1.7/build/lowcode/view.js", + "https://g.alicdn.com/code/npm/@alife/fusion-ui/0.1.7/build/lowcode/view.css" + ], + "urls": [ + "https://g.alicdn.com/code/npm/@alife/fusion-ui/0.1.7/dist/FusionUI.js", + "https://g.alicdn.com/code/npm/@alife/fusion-ui/0.1.7/dist/FusionUI.css" + ], + "library": "FusionUI" + }, + { + "title": "低代码组件 A", + "id": "lcc-a", + "version": "0.1.5", + "type": "lowCode", + "schema": { + "componentsMap": [ + { + "package": "@ali/vc-text", + "componentName": "Text", + "version": "4.1.1" + } + ], + "utils": [ + { + "name": "dataSource", + "type": "npm", + "content": { + "package": "@ali/vu-dataSource", + "exportName": "dataSource", + "version": "1.0.4" + } + } + ], + "componentsTree": [ + { + "defaultProps": { + "content": "这是默认值" + }, + "methods": { + "__initMethods__": { + "compiled": "function (exports, module) { /*set actions code here*/ }", + "source": "function (exports, module) { /*set actions code here*/ }", + "type": "js" + } + }, + "loopArgs": ["item", "index"], + "props": { + "mobileSlot": { + "type": "JSBlock", + "value": { + "children": [ + { + "condition": true, + "hidden": false, + "isLocked": false, + "conditionGroup": "", + "componentName": "Text", + "id": "node_ockxiczf4m2", + "title": "", + "props": { + "maxLine": 0, + "showTitle": false, + "behavior": "NORMAL", + "content": { + "en-US": "Title", + "zh-CN": "页面标题", + "type": "i18n" + }, + "__style__": {}, + "fieldId": "text_kxiczgj4" + } + } + ], + "componentName": "Slot", + "props": { + "slotName": "mobileSlot", + "slotTitle": "mobile 容器" + } + } + }, + "className": "component_k8e4naln", + "useDevice": false, + "fieldId": "symbol_k8bnubw4" + }, + "condition": true, + "children": [ + { + "condition": true, + "loopArgs": [null, null], + "componentName": "Text", + "id": "node_ockxiczf4m4", + "props": { + "maxLine": 0, + "showTitle": false, + "behavior": "NORMAL", + "content": { + "variable": "props.content", + "type": "variable", + "value": { + "use": "zh-CN", + "en-US": "Tips content", + "zh-CN": "这是一个低代码组件", + "type": "i18n" + } + }, + "fieldId": "text_kxid1d9n" + } + } + ], + "propTypes": [ + { + "defaultValue": "这是默认值", + "name": "content", + "title": "文本内容", + "type": "string" + } + ], + "componentName": "Component", + "id": "node_k8bnubvz", + "state": {} + } + ] + }, + "library": "LCCA" + }, + { + "title": "多端组件库", + "package": "@ali/atest1", + "version": "1.23.0", + "advancedUrls": { + "default": [ + "https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css", + "https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/main.3354663.js" + ], + "mobile": [ + "https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css", + "https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/main.mobile.3354663.js" + ], + "rax": [ + "https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css", + "https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/main.rax.3354663.js" + ] + }, + "advancedEditUrls": { + "design": [ + "https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css", + "https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/editView.design.js" + ], + "default": [ + "https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css", + "https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/editView.js" + ] + }, + "library": "Atest1" + }, + { + "library":"UiPaaSServerless3", + "advancedUrls":{ + "default":[ + "https://g.alicdn.com/legao-comp/serverless3/1.1.0/env-staging-d224466e-0614-497d-8cd5-e4036dc50b70/main.js" + ] + }, + "id":"UiPaaSServerless3-view", + "type":"procode", + "version":"1.0.0" + }, + { + "package":"react-color", + "library":"ReactColor", + "id":"react-color", + "type":"procode", + "version":"2.19.3", + "async":true, + "exportMode":"functionCall", + "exportSourceId":"UiPaaSServerless3-view" + } + ] +} +``` + +## 2.3 components (A) + +定义资产包中包含的所有组件的低代码描述的集合,分为“ComponentDescription”和“RemoteComponentDescription”(详见 2.6 TypeScript 定义): + +- ComponentDescription: 符合“组件描述协议”的数据,详见物料规范中`2.2.2 组件描述协议`部分; +- RemoteComponentDescription 是将一个或多个 ComponentDescription 构建打包的 js 资源的描述,在浏览器中加载该资源后可获取到其中包含的每个组件的 ComponentDescription 的具体内容; + +## 2.4 sort (AA) + +定义组件列表分组 + +| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 | +| ----------------- | -------- | -------------------------------------------------------------------------------------------- | -------- | ---------------------------------------- | +| sort.groupList | String[] | 组件分组,用于组件面板 tab 展示 | - | ['精选组件', '原子组件'] | +| sort.categoryList | String[] | 组件面板中同一个 tab 下的不同区间用 category 区分,category 的排序依照 categoryList 顺序排列 | - | ['通用', '数据展示', '表格类', '表单类'] | + +## 2.5 plugins (AAA) + +自定义设计器插件列表 + +| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 | +| --------------------- | --------- | -------------------- | -------- | ------ | +| plugins[].name | String | 插件名称 | - | - | +| plugins[].title | String | 插件标题 | - | - | +| plugins[].description | String | 插件描述 | - | - | +| plugins[].docUrl | String | 插件文档地址 | - | - | +| plugins[].screenshot | String | 插件截图地址 | - | - | +| plugins[].tags | String[] | 插件标签分类 | - | - | +| plugins[].keywords | String[] | 插件检索关键字 | - | - | +| plugins[].reference | Reference | 插件引用的资源包信息 | - | - | + +## 2.6 setters (AAA) + +自定义设置器列表 + +| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 | +| --------------------- | --------- | ---------------------- | -------- | ------ | +| setters[].name | String | 设置器组件名称 | - | - | +| setters[].title | String | 设置器标题 | - | - | +| setters[].description | String | 设置器描述 | - | - | +| setters[].docUrl | String | 设置器文档地址 | - | - | +| setters[].screenshot | String | 设置器截图地址 | - | - | +| setters[].tags | String[] | 设置器标签分类 | - | - | +| setters[].keywords | String[] | 设置器检索关键字 | - | - | +| setters[].reference | Reference | 设置器引用的资源包信息 | - | - | + +## 2.7 extConfig (AAA) + +定义平台相关的扩展内容,用于存放平台自身实现的一些私有协议, 以允许存量平台能够平滑地迁移至标准协议。 extConfig 是一个 key-value 结构的对象,协议不会规定 extConfig 中的字段名称以及类型, 完全自定义 + +## 2.8 TypeScript 定义 + +_组件低代码描述相关部分字段含义详见物料规范中`2.2.2 组件描述协议`部分;_ + +```TypeScript + +/** + * 资产包协议 + */ +export interface Assets { + /** + * 资产包协议版本号 + */ + version: string; + /** + * 资源列表 + */ + packages?: Array; + /** + * 所有组件的描述协议集合 + */ + components: Array; + /** + * 低代码编辑器插件集合 + */ + plugins?: Array; + /** + * 低代码设置器集合 + */ + setters?: Array; + /** + * 平台扩展配置 + */ + extConfig?: AssetsExtConfig; + /** + * 用于描述组件面板中的 tab 和 category + */ + sort: ComponentSort; +} + +export interface AssetsExtConfig{ + [index: string]: any; +} + +/** + * 描述组件面板中的 tab 和 category 排布 + */ +export interface ComponentSort { + /** + * 用于描述组件面板的 tab 项及其排序,例如:["精选组件", "原子组件"] + */ + groupList?: String[]; + /** + * 组件面板中同一个 tab 下的不同区间用 category 区分,category 的排序依照 categoryList 顺序排列; + */ + categoryList?: String[]; +} + +/** + * 定义资产包依赖信息 + */ +export interface Package { + /** + * 唯一标识 + */ + id: string; + /** + * 包名 + */ + package: string; + /** + * 包版本号 + */ + version: string; + /** + * 资源类型 + */ + type: string; + /** + * 组件渲染态视图打包后的 CDN url 列表,包含 js 和 css + */ + urls?: string[] | any; + /** + * 组件多个渲染态视图打包后的 CDN url 列表,包含 js 和 css,优先级高于 urls + */ + advancedUrls?: ComplexUrls; + /** + * 组件编辑态视图打包后的 CDN url 列表,包含 js 和 css + */ + editUrls?: string[] | any; + /** + * 组件多个编辑态视图打包后的 CDN url 列表,包含 js 和 css,优先级高于 editUrls + */ + advancedEditUrls?: ComplexUrls; + /** + * 低代码组件的 schema 内容 + */ + schema?: ComponentSchema; + /** + * 当前资源所依赖的其他资源包的 id 列表 + */ + deps?: string[]; + /** + * 指定当前资源加载的环境 + */ + loadEnv?: LoadEnv[]; + /** + * 当前资源是否是 external 资源 + */ + external?: boolean; + /** + * 作为全局变量引用时的名称,和 webpack output.library 字段含义一样,用来定义全局变量名 + */ + library: string; + /** + * 组件描述导出名字,可以通过 window[exportName] 获取到组件描述的 Object 内容; + */ + exportName?: string; + /** + * 标识当前 package 资源加载在 window.library 上的是否是一个异步对象 + */ + async?: boolean; + /** + * 标识当前 package 从其他 package 的导出方式 + */ + exportMode?: string; + /** + * 标识当前 package 内容是从哪个 package 导出来的 + */ + exportSourceId?: string; + /** + * 标识当前 package 是从 window 上的哪个属性导出来的 + */ + exportSourceLibrary?: string; +} + + +/** + * 复杂 urls 结构,同时兼容简单结构和多模态结构 + */ +export type ComplexUrls = string[] | MultiModeUrls; + +/** + * 多模态资源 + */ +export interface MultiModeUrls { + /** + * 默认的资源 url + */ + default: string[]; + /** + * 其他模态资源的 url + */ + [index: string]: string[]; +} + + +/** + * 资源加载环境种类 + */ +export enum LoadEnv { + /** + * 设计态 + */ + design = "design", + /** + * 运行态 + */ + runtime = "runtime" +} + +/** + * 低代码设置器描述 + */ +export type SetterDescription = PluginDescription; + +/** + * 低代码插件器描述 + */ +export interface PluginDescription { + /** + * 插件名称 + */ + name: string; + /** + * 插件标题 + */ + title: string; + /** + * 插件类型 + */ + type?: string; + /** + * 插件描述 + */ + description?: string; + /** + * 插件文档地址 + */ + docUrl: string; + /** + * 插件截图 + */ + screenshot: string; + /** + * 插件相关的标签 + */ + tags?: string[]; + /** + * 插件关键字 + */ + keywords?: string[]; + /** + * 插件引用的资源信息 + */ + reference: Reference; +} + +/** + * 资源引用信息,Npm 的升级版本, + */ +export interface Reference { + /** + * 引用资源的 id 标识 + */ + id?: string; + /** + * 引用资源的包名 + */ + package?: string; + /** + * 引用资源的导出对象中的属性值名称 + */ + exportName: string; + /** + * 引用 exportName 上的子对象 + */ + subName: string; + /** + * 引用的资源主入口 + */ + main?: string; + /** + * 是否从引用资源的导出对象中获取属性值 + */ + destructuring: boolean; + /** + * 资源版本号 + */ + version: string; +} + + +/** + * 低代码片段 + * + * 内容为组件不同状态下的低代码 schema (可以有多个),用户从组件面板拖入组件到设计器时会向页面 schema 中插入 snippets 中定义的组件低代码 schema + */ +export interface Snippet { + title: string; + screenshot?: string; + schema: ElementJSON; +} + +/** + * 组件低代码描述 + */ +export interface ComponentDescription { + componentName: string; + title: string; + description?: string; + docUrl: string; + screenshot: string; + icon?: string; + tags?: string[]; + keywords?: string[]; + devMode?: 'proCode' | 'lowCode'; + npm: Npm; + props: Prop[]; + configure: Configure; + /** + * 多模态下的组件描述, 优先级高于 configure + */ + advancedConfigures: MultiModeConfigures; + snippets: Snippet[]; + group: string; + category: string; + priority: number; + /** + * 组件引用的资源信息 + */ + reference: Reference; +} + +export interface MultiModeConfigures { + default: Configure; + [index: string]: Configure; +} + +/** + * 远程物料描述 + */ +export interface RemoteComponentDescription { + /** + * 组件描述导出名字,可以通过 window[exportName] 获取到组件描述的 Object 内容; + */ + exportName?: string; + /** + * 组件描述的资源链接; + */ + url?: string; + /** + * 组件多模态描述的资源信息,优先级高于 url + */ + advancedUrls?: ComplexUrl; + /** + * 组件(库)的 npm 信息; + */ + package?: { + npm?: string; + }; +} + +export type ComplexUrl = string | MultiModeUrl + +export interface MultiModeUrl { + default: string; + [index: string]: string; +} + +export interface ComponentSchema { + version: string; + componentsMap: ComponentsMap; + componentsTree: [ComponentTree]; + i18n: I18nMap; + utils: UtilItem[]; +} + +``` + +`ComponentSchema` 的定义见[低代码业务组件描述](./1.material-spec.md#221-组件规范) diff --git a/specs/lowcode-spec.md b/specs/lowcode-spec.md new file mode 100644 index 000000000..65e05130d --- /dev/null +++ b/specs/lowcode-spec.md @@ -0,0 +1,1462 @@ +# 《低代码引擎搭建协议规范》 + +# 1 介绍 + +## 1.1 本协议规范涉及的问题域 + +- 定义本协议版本号规范 +- 定义本协议中每个子规范需要被支持的 Level +- 定义本协议相关的领域名词 +- 定义搭建基础协议版本号规范(A) +- 定义搭建基础协议组件映射关系规范(A) +- 定义搭建基础协议组件树描述规范(A) +- 定义搭建基础协议国际化多语言支持规范(AA) +- 定义搭建基础协议无障碍访问规范(AAA) + + +## 1.2 协议草案起草人 + +- 撰写:月飞、康为、林熠 +- 审阅:大果、潕量、九神、元彦、戊子、屹凡、金禅、前道、天晟、戊子、游鹿、光弘、力皓 + + +## 1.3 版本号 + +1.0.0 + +## 1.4 协议版本号规范(A) + +本协议采用语义版本号,版本号格式为 `major.minor.patch` 的形式。 + +- major 是大版本号:用于发布不向下兼容的协议格式修改 +- minor 是小版本号:用于发布向下兼容的协议功能新增 +- patch 是补丁号:用于发布向下兼容的协议问题修正 + + +## 1.5 协议中子规范 Level 定义 + +| 规范等级 | 实现要求 | +| -------- | ---------------------------------------------------------------------------------- | +| A | 强制规范,必须实现;违反此类规范的协议描述数据将无法写入物料中心,不支持流通。 | +| AA | 推荐规范,推荐实现;遵守此类规范有助于业务未来的扩展性和跨团队合作研发效率的提升。 | +| AAA | 参考规范,根据业务场景实际诉求实现;是集团层面鼓励的技术实现引导。 | + + +## 1.6 名词术语 + +### 1.6.1 物料系统名词 + +- **基础组件(Basic Component)**:前端领域通用的基础组件,阿里巴巴前端委员会官方指定的基础组件库是 Fusion Next/AntD。 +- **图表组件(Chart Component)**:前端领域通用的图表组件,有代表性的图表组件库有 BizCharts。 +- **业务组件(Business Component)**:业务领域内基于基础组件之上定义的组件,可能会包含特定业务域的交互或者是业务数据,对外仅暴露可配置的属性,且必须发布到公域(如阿里 NPM);在同一个业务域内可以流通,但不需要确保可以跨业务域复用。 + - **低代码业务组件(Low-Code Business Component)**:通过低代码编辑器搭建而来,有别于源码开发的业务组件,属于业务组件中的一种类型,遵循业务组件的定义;同时低代码业务组件还可以通过低代码编辑器继续多次编辑。 +- **布局组件(Layout Component)**:前端领域通用的用于实现基础组件、图表组件、业务组件之间各类布局关系的组件,如三栏布局组件。 +- **区块(Block)**:通过低代码搭建的方式,将一系列业务组件、布局组件进行嵌套组合而成,不对外提供可配置的属性。可通过 区块容器组的包裹,实现区块内部具备有完整的样式、事件、生命周期管理、状态管理、数据流转机制。能独立存在和运行,可通过复制 schema 实现跨页面、跨应用的快速复用,保障功能和数据的正常。 +- **页面(Page)**:由组件 + 区块组合而成。由页面容器组件包裹,可描述页面级的状态管理和公共函数。 +- **模板(Template)**:特定垂直业务领域内的业务组件、区块可组合为单个页面,或者是再配合路由组合为多个页面集,统称为模板。 + + +### 1.6.2 低代码搭建系统名词 + +- **搭建编辑器**:使用可视化的方式实现页面搭建,支持组件 UI 编排、属性编辑、事件绑定、数据绑定,最终产出符合搭建基础协议规范的数据。 + - **属性面板**:低代码编辑器内部用于组件、区块、页面的属性编辑、事件绑定、数据绑定的操作面板。 + - **画布面板**:低代码编辑器内部用于 UI 编排的操作面板。 + - **大纲面板**:低代码编辑器内部用于页面组件树展示的面板。 +- **编辑器框架**:搭建编辑器的基础框架,包含主题配置机制、插件机制、setter 控件机制、快捷键管理、扩展点管理等底层基础设施。 +- **入料模块**:专注于物料接入,能自动扫描、解析源码组件,并最终产出一份符合《低代码引擎物料协议规范》的 Schema JSON。 +- **编排模块**:专注于 Schema 可视化编排,以可视化的交互方式提供页面结构编排服务,并最终产出一份符合《低代码搭建基础协议规范》的 Schema JSON。 +- **渲染模块**:专注于将 Schema JSON 渲染为 UI 界面,最终呈现一个可交互的页面。 +- **出码模块 Schema2Code**:专注于通过 Schema JSON 生成高质量源代码,将符合《低代码搭建基础协议规范》的 Schema JSON 数据分别转化为面向 React / Rax / 阿里小程序等终端可渲染的代码。 +- **事件绑定**:是指为某个组件的某个事件绑定相关的事件处理动作,比如为某个组件的**点击事件**绑定**一段处理函数**或**响应动作**(比如弹出对话框),每个组件可绑定的事件由该组件自行定义。 +- **数据绑定**:是指为某个组件的某个属性绑定用于该属性使用的数据。 +- **生命周期**: 一般指某个对象的生老病死,本文中指某个实体(组件、容器、区块等等)的创建、加载、显示、销毁等关键生命阶段的统称。 + +## 1.7 背景 + +- **协议目标**: 通过约束低代码引擎的搭建协议规范,让上层低代码编辑器的产出物(低代码业务组件、区块、应用)保持一致性,可跨低代码研发平台进行流通而提效,亦不阻碍集团业务间融合的发展。  +- **协议通**: + - 协议顶层结构统一 + - 协议 schema 具备有完整的描述能力,包含版本、国际化、组件树、组件映射关系等; + - 顶层属性 key、value 值的格式,必须保持一致; + - 组件树描述统一 + - 源码组件描述; + - 页面、区块、低代码业务组件这三种容器组件的描述; + - 数据流描述,包含数据请求、数据状态管理、数据绑定描述; + - 事件描述,包含统一事件上下文、统一搭建 API; +- **物料通**:指在相同领域内的不同搭建产品,可直接使用的物料。比如模版、区块、组件; + +## 1.8 受众 + +本协议适用于所有使用低代码搭建平台来开发页面或组件的开发者,以及围绕此协议的相关工具或工程化方案的开发者。阅读及使用本协议,需要对低代码搭建平台的交互和实现有一定的了解,对前端开发相关技术栈的熟悉也会有帮助,协议中对通用的前端相关术语不会做进一步的解释说明。 + +## 1.9 使用范围 + +本协议描述的是低代码搭建平台产物(应用、页面、区块、组件)的 schema 结构,以及实现其数据状态更新(内置 api)、能力扩展、国际化等方面完整,只在低代码搭建场景下可用; + +## 1.10 协议目标 + +一套面向开发者的 schema 规范,用于规范化约束搭建编辑器的输出,以及渲染模块和出码模块的输入,将搭建编辑器、渲染模块、出码模块解耦,保障搭建编辑器、渲染模块、出码模块的独立升级。 + +## 1.11 设计说明 + +- **语义化**:语义清晰,简明易懂,可读性强。 +- **渐进性描述**:搭建的本质是通过 源码组件 进行嵌套组合,从小往大、依次组合生成 组件、区块、页面,最终通过云端构建生成 应用 的过程。因此在搭建基础协议中,我们需要知道如何去渐进性的描述组件、区块、页面、应用这 4 个实体概念。 +- **生成标准源码**:明确每一个属性与源码对应的转换关系,可生成跟手写无差异的高质量标准源代码。 +- **可流通性**:产物能在不同搭建产品中流通,不涉及任何私域数据存储。 +- **面向多端**:不能仅面向 React,还有小程序等多端。 +- **支持国际化&无障碍访问标准的实现** + + +# 2 协议结构 + +协议最顶层结构如下,包含5方面的描述内容: + +- version { String } 当前协议版本号 +- componentsMap { Array } 组件映射关系 +- componentsTree { Array } 描述模版/页面/区块/低代码业务组件的组件树 +- utils { Array } 工具类扩展映射关系 +- i18n { Object } 国际化语料 + + +描述举例: + +```json +{ + "version": "1.0.0", // 当前协议版本号 + "componentsMap": [{ // 组件描述 + "componentName": "Button", + "package": "@alifd/next", + "version": "1.0.0", + "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": "@alifd/next", + "version": "0.0.1", + "exportName": "Moment", + "subName": "", + "destructuring": true, + "main": "" + } + }], + "componentsTree": [{ // 描述内容,值类型 Array + "componentName": "Page", // 单个页面,枚举类型 Page|Block|Component + "fileName": "Page1", + "props": {}, + "css": "body {font-size: 12px;} .table { width: 100px;}", + "children": [{ + "componentName": "Div", + "props": { + "className": "" + }, + "children": [{ + "componentName": "Button", + "props": { + "prop1": 1234, // 简单 json 数据 + "prop2": [{ // 简单 json 数据 + "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": "JSFunction", + "value": "function(e) { console.log(e.target.innerText) }" + }, + "onClick2": { // 动作绑定 2 + "type": "JSExpression", + "value": "this.submit" + } + } + }] + }] + }], + "i18n": { + "zh-CN": { + "i18n-jwg27yo4": "你好", + "i18n-jwg27yo3": "中国" + }, + "en-US": { + "i18n-jwg27yo4": "Hello", + "i18n-jwg27yo3": "China" + } + } +} +``` + +## 2.1 协议版本号(A) + +定义当前协议 schema 的版本号,不同的版本号对应不同的渲染 SDK,以保障不同版本搭建协议产物的正常渲染; + + +| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 | +| ---------- | ------ | ---------- | -------- | ------ | +| version | String | 协议版本号 | - | 1.0.0 | + + +描述示例: + +```javascript +{ + "version": "1.0.0" +} +``` + +## 2.2 组件映射关系(A) + +协议中用于描述 componentName 到公域组件映射关系的规范。 + + +| 参数 | 说明 | 类型 | 变量支持 | 默认值 | +| --------------- | ---------------------- | ------------------------- | -------- | ------ | +| componentsMap[] | 描述组件映射关系的集合 | Array\<**ComponentMap**\> | - | null | + +**ComponentMap 结构描述**如下: + +| 参数 | 说明 | 类型 | 变量支持 | 默认值 | +| ------------- | ------------------------------------------------------------------------------------------------------ | ------- | -------- | ------ | +| componentName | 协议中的组件名,唯一性,对应包导出的组件名,是一个有效的 **JS 标识符**,而且是大写字母打头 | String | - | - | +| package | npm 公域的 package name | String | - | - | +| version | package version | String | - | - | +| destructuring | 使用解构方式对模块进行导出 | Boolean | - | - | +| exportName | 包导出的组件名 | String | - | - | +| subName | 下标子组件名称 | String | - | | +| main | 包导出组件入口文件路径 | String | - | - | + + +描述示例: + +```json +{ + "componentsMap": [{ + "componentName": "Button", + "package": "@alifd/next", + "version": "1.0.0", + "destructuring": true + }, { + "componentName": "MySelect", + "package": "@alifd/next", + "version": "1.0.0", + "destructuring": true, + "exportName": "Select" + }, { + "componentName": "ButtonGroup", + "package": "@alifd/next", + "version": "1.0.0", + "destructuring": true, + "exportName": "Button", + "subName": "Group" + }, { + "componentName": "RadioGroup", + "package": "@alifd/next", + "version": "1.0.0", + "destructuring": true, + "exportName": "Radio", + "subName": "Group" + }, { + "componentName": "CustomCard", + "package": "@ali/custom-card", + "version": "1.0.0" + }, { + "componentName": "CustomInput", + "package": "@ali/custom", + "version": "1.0.0", + "main": "/lib/input", + "destructuring": true, + "exportName": "Input" + }] +} +``` + +出码结果: + +```javascript +// 使用解构方式, destructuring is true. +import { Button } from '@alifd/next'; + +// 使用解构方式,且 exportName 和 componentName 不同 +import { Select as MySelect } from '@alifd/next'; + +// 使用解构方式,并导出其子组件 +import { Button } from '@alifd/next'; +const ButtonGroup = Button.Group; + +import { Radio } from '@alifd/next'; +const RadioGroup = Radio.Group; + +// 不使用解构方式进行导出 +import CustomCard from '@ali/custom-card'; + +// 使用特定路径进行导出 +import { Input as CustomInput } from '@ali/custom/lib/input'; + +``` + + +## 2.3 组件树描述(A) + + +协议中用于描述搭建出来的组件树结构的规范,整个组件树的描述由**组件结构**&**容器结构**两种结构嵌套构成。 + +- 组件结构:描述单个组件的名称、属性、子集的结构; +- 容器结构:描述单个容器的数据、自定义方法、生命周期的结构,用于将完整页面进行模块化拆分。 + +与源码对应的转换关系如下: + +- 组件结构:转换成一个 .jsx 文件内 React Class 类 render 函数返回的 **jsx** 代码。 +- 容器结构:将转换成一个标准文件,如 React 的 jsx 文件, export 一个 React Class,包含生命周期定义、自定义方法、事件属性绑定、异步数据请求等。 + +### 2.3.1 基础结构描述 (A) + +此部分定义了组件结构、容器结构的公共基础字段。 + +> 阅读时可先跳到后续章节,待需要时回来参考阅读 + +#### 2.3.1.1 Props 结构描述 + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | 备注 | +| ----------- | ------------ | ------ | -------- | ------ | ------------------------------------- | +| id | 组件 ID | String | ✅ | - | 系统属性 | +| className | 组件样式类名 | String | ✅ | - | 系统属性,支持变量表达式 | +| style | 组件内联样式 | Object | ✅ | - | 系统属性,单个内联样式属性值 | +| ref | 组件 ref 名称 | String | ✅ | - | 可通过 `this.$(ref)` 获取组件实例 | +| extendProps | 组件继承属性 | 变量 | ✅ | - | 仅支持变量绑定,常用于继承属性对象 | +| ... | 组件私有属性 | - | - | - | | + +#### 2.3.1.2 css/less/scss 样式描述 + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | +| ------------- | -------------------------------------------------------------------------- | ------ | -------- | ------ | +| css/less/scss | 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,不支持 @import | String | - | null | + +描述示例: + +```json +{ + "css": "body {font-size: 12px;} .table { width: 100px; }" +} +``` + +#### 2.3.1.3 ComponentDataSource 对象描述 + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | 备注 | +| ----------- | ---------------------- | -------------------------------------- | -------- | ------ | ----------------------------------------------------------------------------------------------------------- | +| list[] | 数据源列表 | Array\<**ComponentDataSourceItem**\> | - | - | 成为为单个请求配置, 内容定义详见 [ComponentDataSourceItem 对象描述](#2314-componentdatasourceitem-对象描述) | +| dataHandler | 所有请求数据的处理函数 | Function | - | - | 详见 [dataHandler Function 描述](#2317-datahandler-function 描述) | + +#### 2.3.1.4 ComponentDataSourceItem 对象描述 + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | 备注 | +| -------------- | ---------------------------- | ---------------------------------------------------- | -------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| id | 数据请求 ID 标识 | String | - | - | | +| isInit | 是否为初始数据 | Boolean | ✅ | true | 值为 true 时,将在组件初始化渲染时自动发送当前数据请求 | +| isSync | 是否需要串行执行 | Boolean | ✅ | false | 值为 true 时,当前请求将被串行执行 | +| type | 数据请求类型 | String | - | fetch | 支持四种类型:fetch/mtop/jsonp/custom | +| shouldFetch | 本次请求是否可以正常请求 | (options: ComponentDataSourceItemOptions) => boolean | - | ```() => true``` | function 参数参考 [ComponentDataSourceItemOptions 对象描述](#2315-componentdatasourceitemoptions-对象描述) | +| willFetch | 单个数据结果请求参数处理函数 | Function | - | options => options | 只接受一个参数(options),返回值作为请求的 options,当处理异常时,使用原 options。也可以返回一个 Promise,resolve 的值作为请求的 options,reject 时,使用原 options | +| requestHandler | 自定义扩展的外部请求处理器 | Function | - | - | 仅 type='custom' 时生效 | +| dataHandler | request 成功后的回调函数 | Function | - | `response => response.data` | 参数: 请求成功后 promise 的 value 值 | +| errorHandler | request 失败后的回调函数 | Function | - | - | 参数: 请求出错 promise 的 error 内容 | +| options {} | 请求参数 | **ComponentDataSourceItemOptions** | - | - | 每种请求类型对应不同参数, 详见 [ComponentDataSourceItemOptions 对象描述](#2315-componentdatasourceitemoptions-对象描述) | + +**关于 dataHandler 于 errorHandler 的细节说明:** + +request 返回的是一个 promise,dataHandler 和 errorHandler 遵循 Promise 对象的 then 方法,实际使用方式如下: + +```ts +// 伪代码 +try { + const result = await request(fetchConfig).then(dataHandler, errorHandler); + dataSourceItem.data = result; + dataSourceItem.status = 'success'; +} catch (err) { + dataSourceItem.error = err; + dataSourceItem.status = 'error'; +} +``` +**注意:** +- dataHandler 和 errorHandler 只会走其中的一个回调 +- 它们都有修改 promise 状态的机会,意味着可以修改当前数据源最终状态 +- 最后返回的结果会被认为是当前数据源的最终结果,如果被 catch 了,那么会认为数据源请求出错 +- dataHandler 会有默认值,考虑到返回结果入参都是 response 完整对象,默认值会返回 `response.data`,errorHandler 没有默认值 + + +#### 2.3.1.5 ComponentDataSourceItemOptions 对象描述 + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | 备注 | +| ------- | ------------ | ------- | -------- | ------ | ----------------------------------------------------------------------------------------------------------- | +| uri | 请求地址 | String | ✅ | - | | +| params | 请求参数 | Object | ✅ | {} | 当前数据源默认请求参数(在运行时会被实际的 load 方法的参数替换,如果 load 的 params 没有则会使用当前 params) | +| method | 请求方法 | String | ✅ | GET | | +| isCors | 是否支持跨域 | Boolean | ✅ | true | 对应 `credentials = 'include'` | +| timeout | 超时时长 | Number | ✅ | 5000 | 单位 ms | +| headers | 请求头信息 | Object | ✅ | - | 自定义请求头 | + + + +#### 2.3.1.6 ComponentLifeCycles 对象描述 + +生命周期对象,schema 面向多端,不同 DSL 有不同的生命周期方法: + +- React:对于中后台 PC 物料,已明确使用 React 作为最终渲染框架,因此提案采用 [React16 标准生命周期方法](https://reactjs.org/docs/react-component.html)标准来定义生命周期方法,降低理解成本,支持生命周期如下: + - constructor(props, context)  + - 说明:初始化渲染时执行,常用于设置 state 值。 + - render()  + - 说明:执行于容器组件 React Class 的 render 方法最前,常用于计算变量挂载到 this 对象上,供 props 上属性绑定。此 render() 方法不需要设置 return 返回值。 + - componentDidMount() + - 说明:组件已加载 + - componentDidUpdate(prevProps, prevState, snapshot) + - 说明:组件已更新 + - componentWillUnmount() + - 说明:组件即将从 DOM 中移除 + - componentDidCatch(error, info) + - 说明:组件捕获到异常 +- Rax:目前没有使用生命周期,使用 hooks 替代生命周期; + +该对象由一系列 key-value 组成,key 为生命周期方法名,value 为 JSFunction 的描述,详见下方示例: + +```json +{ + "componentDidMount": { // key 为上文中 React 的生命周期方法名 + "type": "JSFunction", // type 目前仅支持 JSFunction + "value": "function() {\ // value 为 javascript 函数 + console.log('did mount');\ + }" + }, + "componentWillUnmount": { + "type": "JSFunction", + "value": "function() {\ + console.log('will unmount');\ + }" + } + ... +}, +``` + + +#### 2.3.1.7 dataHandler Function 描述 + +- 参数:为 dataMap 对象,包含字段如下: + - key: 数据 id + - value: 单个请求结果 +- 返回值:数据对象 data,将会在渲染引擎和 schemaToCode 中通过调用 `this.setState(...)` 将返回的数据对象生效到 state 中;支持返回一个 Promise,通过 `resolve(返回数据)`,常用于串行发送请求场景。 + +#### 2.3.1.8 ComponentPropDefinition 对象描述 + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | 备注 | +| ------------ | ---------- | -------------- | -------- | --------- | ----------------------------------------------------------------------------------------------------------------- | +| name | 属性名称 | String | - | - | | +| propType | 属性类型 | String\|Object | - | - | 具体值内容结构,参考《低代码引擎物料协议规范》 内的 “2.2.2.3 组件属性信息”中描述的**基本类型**和**复合类型** | +| description | 属性描述 | String | - | '' | | +| defaultValue | 属性默认值 | Any | - | undefined | 当 defaultValue 和 defaultProps 中存在同一个 prop 的默认值时,优先使用 defaultValue。 | + +范例: +```json +{ + "propDefinitions": [{ + "name": "title", + "propType": "string", + "defaultValue": "Default Title" + }, { + "name": "onClick", + "propType": "func" + }] + ... +}, +``` + +### 2.3.2 组件结构描述(A) + +对应生成源码开发体系中 render 函数返回的 jsx 代码,主要描述有以下属性: + + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | 备注 | +| ------------- | ---------------------- | ---------------- | -------- | ----------------- | ---------------------------------------------------------------------------------------------------------- | +| id | 组件唯一标识 | String | - | | 可选, 组件 id 由引擎随机生成(UUID),并保证唯一性,消费方为上层应用平台,在组件发生移动等场景需保持 id 不变 | +| componentName | 组件名称 | String | - | Div | 必填,首字母大写, 同 [componentsMap](#22-组件映射关系 a) 中的要求 | +| props {} | 组件属性对象 | **Props** | - | {} | 必填, 详见 [Props 结构描述](#2311-props-结构描述) | +| condition | 渲染条件 | Boolean | ✅ | true | 选填,根据表达式结果判断是否渲染物料;支持变量表达式 | +| loop | 循环数据 | Array | ✅ | - | 选填,默认不进行循环渲染;支持变量表达式 | +| loopArgs | 循环迭代对象、索引名称 | [String, String] | | ["item", "index"] | 选填,仅支持字符串 | +| children | 子组件 | Array | | | 选填,支持变量表达式 | + + +描述举例: + +```json +{ + "componentName": "Button", + "props": { + "className": "btn", + "style": { + "width": 100, + "height": 20 + }, + "text": "submit", + "onClick": { + "type": "JSFunction", + "value": "function(e) {\ + console.log('btn click')\ + }" + } + }, + "condition": { + "type": "JSExpression", + "value": "!!this.state.isshow" + }, + "loop": [], + "loopArgs": ["item", "index"], + "children": [] +} +``` + + +### 2.3.3 容器结构描述 (A)  + +容器是一类特殊的组件,在组件能力基础上增加了对生命周期对象、自定义方法、样式文件、数据源等信息的描述。包含**低代码业务组件容器 Component**、**区块容器 Block**、**页面容器 Page** 3 种。主要描述有以下属性: + +- 组件类型:componentName +- 文件名称:fileName +- 组件属性:props +- state 状态管理:state +- 生命周期 Hook 方法:lifeCycles +- 自定义方法设置:methods +- 异步数据源配置:dataSource +- 条件渲染:condition +- 样式文件:css/less/scss + + +详细描述: + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | 备注 | +| --------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------- | -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- | +| componentName | 组件名称 | 枚举类型,包括`'Page'` (代表页面容器)、`'Block'` (代表区块容器)、`'Component'` (代表低代码业务组件容器) | - | 'Div' | 必填,首字母大写 | +| fileName | 文件名称 | String | - | - | 必填,英文 | +| props { } | 组件属性对象 | **Props** | - | {} | 必填,详见 [Props 结构描述](#2311-props-结构描述) | +| static | 低代码业务组件类的静态对象 | | | | | +| defaultProps | 低代码业务组件默认属性 | Object | - | - | 选填,仅用于定义低代码业务组件的默认属性 | +| propDefinitions | 低代码业务组件属性类型定义 | **Array\** | - | - | 选填,仅用于定义低代码业务组件的属性数据类型。详见 [ComponentPropDefinition 对象描述](#2318-componentpropdefinition-对象描述) | +| condition | 渲染条件 | Boolean | ✅ | true | 选填,根据表达式结果判断是否渲染物料;支持变量表达式 | +| state | 容器初始数据 | Object | ✅ | - | 选填,支持变量表达式 | +| children | 子组件 | Array | - | | 选填,支持变量表达式 | +| css/less/scss | 样式属性 | String | ✅ | - | 选填, 详见 [css/less/scss 样式描述](#2312-csslessscss 样式描述) | +| lifeCycles | 生命周期对象 | **ComponentLifeCycles** | - | - | 详见 [ComponentLifeCycles 对象描述](#2316-componentlifecycles-对象描述) | +| methods | 自定义方法对象 | Object | - | - | 选填,对象成员为函数类型 | +| dataSource {} | 数据源对象 | **ComponentDataSource** | - | - | 选填,异步数据源, 详见 [ComponentDataSource 对象描述](#2313-componentdatasource-对象描述) | + + + +#### 完整描述示例 + +描述示例 1:(正常 fetch/mtop/jsonp 请求): + +```json +{ + "componentName": "Block", + "fileName": "block-1", + "props": { + "className": "luna-page", + "style": { + "background": "#dd2727" + } + }, + "children": [{ + "componentName": "Button", + "props": { + "text": { + "type": "JSExpression", + "value": "this.state.btnText" + } + } + }], + "state": { + "btnText": "submit" + }, + "css": "body {font-size: 12px;}", + "lifeCycles": { + "componentDidMount": { + "type": "JSFunction", + "value": "function() {\ + console.log('did mount');\ + }" + }, + "componentWillUnmount": { + "type": "JSFunction", + "value": "function() {\ + console.log('will unmount');\ + }" + } + }, + "methods": { + "testFunc": { + "type": "JSFunction", + "value": "function() {\ + console.log('test func');\ + }" + } + }, + "dataSource": { + "list": [{ + "id": "list", + "isInit": true, + "type": "fetch/mtop/jsonp", + "options": { + "uri": "", + "params": {}, + "method": "GET", + "isCors": true, + "timeout": 5000, + "headers": {} + }, + "dataHandler": { + "type": "JSFunction", + "value": "function(data, err) {}" + } + }], + "dataHandler": { + "type": "JSFunction", + "value": "function(dataMap) { }" + } + }, + "condition": { + "type": "JSExpression", + "value": "!!this.state.isShow" + } +} +``` + +描述示例 2:(自定义扩展请求处理器类型): + +```json +{ + "componentName": "Block", + "fileName": "block-1", + "props": { + "className": "luna-page", + "style": { + "background": "#dd2727" + } + }, + ... + "dataSource": { + "list": [{ + "id": "list", + "isInit": true, + "type": "custom", + "requestHandler": { + "type": "JSFunction", + "value": "this.utils.hsfHandler" + }, + "options": { + "uri": "hsf://xxx", + "param1": "a", + "param2": "b", + ... + }, + "dataHandler": { + "type": "JSFunction", + "value": "function(data, err) { }" + } + }], + "dataHandler": { + "type": "JSFunction", + "value": "function(dataMap) { }" + } + } +} +``` + +### 2.3.4 属性值类型描述(A) + +在上述**组件结构**和**容器结构**描述中,每一个属性所对应的值,除了传统的 JS 值类型(String、Number、Object、Array、Boolean)外,还包含有**节点类型**、**事件函数类型**、**变量类型**等多种复杂类型;接下来将对于复杂类型的详细描述方式进行详细介绍。 + +#### 2.3.4.1 节点类型(A) + +通常用于描述组件的某一个属性为 **ReactNode** 或 **Function-Return-ReactNode** 的场景。该类属性的描述均以 **JSSlot** 的方式进行描述,详细描述如下: + +**ReactNode** 描述: + +| 参数 | 说明 | 值类型 | 默认值 | 备注 | +| ----- | ---------- | --------------------- | -------- | -------------------------------------------------------------- | +| type | 值类型描述 | String | 'JSSlot' | 固定值 | +| value | 具体的值 | Array\ | null | 内容为 NodeSchema 类型,详见[组件结构描述](#232-组件结构描述(A)) | + + +举例描述:如 **Card** 的 **title** 属性 + +```json +{ + "componentName": "Card", + "props": { + "title": { + "type": "JSSlot", + "value": [{ + "componentName": "Icon", + "props": {} + },{ + "componentName": "Text", + "props": {} + }] + }, + ... + } +} + +``` + + +**Function-Return-ReactNode** 描述: + +| 参数 | 说明 | 值类型 | 默认值 | 备注 | +| ------ | ---------- | --------------------- | -------- | -------------------------------------------------------------- | +| type | 值类型描述 | String | 'JSSlot' | 固定值 | +| value | 具体的值 | Array\ | null | 内容为 NodeSchema 类型,详见[组件结构描述](#232-组件结构描述 a) | +| params | 函数的参数 | Array\ | null | 函数的入参,其子节点可以通过 `this[参数名]` 来获取对应的参数。 | + + +举例描述:如 **Table.Column** 的 **cell** 属性 + +```json +{ + "componentName": "TabelColumn", + "props": { + "cell": { + "type": "JSSlot", + "params": ["value", "index", "record"], + "value": [{ + "componentName": "Input", + "props": {} + }] + }, + ... + } +} + +``` + +#### 2.4.3.2 事件函数类型(A) + +协议内的事件描述,主要包含**容器结构**的**生命周期**和**自定义方法**,以及**组件结构**的**事件函数类属性**三类。所有事件函数的描述,均以 **JSFunction** 的方式进行描述,保留与原组件属性、生命周期(React / 小程序)一致的输入参数,并给所有事件函数 binding 统一一致的上下文(当前组件所在容器结构的 **this** 对象)。 + +**事件函数类型**的属性值描述如下: + +```json +{ + "type": "JSFunction", + "value": "function onClick(){\ + console.log(123);\ + }" +} +``` + +描述举例: + +```json +{ + "componentName": "Block", + "fileName": "block1", + "props": {}, + "state": { + "name": "lucy" + }, + "lifeCycles": { + "componentDidMount": { + "type": "JSFunction", + "value": "function() {\ + console.log('did mount');\ + }" + }, + "componentWillUnmount": { + "type": "JSFunction", + "value": "function() {\ + console.log('will unmount');\ + }" + } + }, + "methods": { + "getNum": { + "type": "JSFunction", + "value": "function() {\ + console.log('名称是:' + this.state.name)\ + }" + } + }, + "children": [{ + "componentName": "Button", + "props": { + "text": "按钮", + "onClick": { + "type": "JSFunction", + "value": "function(e) {\ + console.log(e.target.innerText);\ + }" + } + } + }] +} +``` + +#### 2.4.3.3 变量类型(A) + +在上述**组件结构** 或**容器结构**中,有多个属性的值类型是支持变量类型的,通常会通过变量形式来绑定某个数据,所有的变量表达式均通过 JSExpression 表达式,上下文与事件函数描述一致,表达式内通过 **this** 对象获取上下文; + +变量**类型**的属性值描述如下: + + +- return 数字类型 + + ```json + { + "type": "JSExpression", + "value": "this.state.num" + } + ``` +- return 数字类型 + + ```json + { + "type": "JSExpression", + "value": "this.state.num - this.state.num2" + } + ``` +- return "8万" 字符串类型 + + ```json + { + "type": "JSExpression", + "value": "`${this.state.num}万`" + } + ``` +- return "8万" 字符串类型 + + ```json + { + "type": "JSExpression", + "value": "this.state.num + '万'" + } + ``` +- return 13 数字类型 + + ```json + { + "type": "JSExpression", + "value": "getNum(this.state.num, this.state.num2)" + } + ``` +- return true 布尔类型 + + ```json + { + "type": "JSExpression", + "value": "this.state.num > this.state.num2" + } + ``` + +描述举例: + +```json +{ + "componentName": "Block", + "fileName": "block1", + "props": {}, + "state": { + "num": 8, + "num2": 5 + }, + "methods": { + "getNum": { + "type": "JSFunction", + "value": "function(a, b){\ + return a + b;\ + }" + } + }, + "children": [{ + "componentName": "Button", + "props": { + "text": { + "type": "JSExpression", + "value": "getNum(this.state.num, this.state.num2) + '万'" + } + }, + "condition": { + "type": "JSExpression", + "value": "this.state.num > this.state.num2" + } + }] +} +``` + +#### 2.4.3.4 国际化多语言类型(AA) + +协议内的一些文本值内容,我们希望是和协议全局的国际化多语言语料是关联的,会按照全局国际化语言环境的不同使用对应的语料。所有国际化多语言值均以 **i18n** 结构描述。这样可以更为清晰且结构化得表达使用场景。 + +**国际化多语言类型**的属性值类型描述如下: + +```typescript +type Ti18n = { + type: 'i18n'; + key: string; // i18n 结构中字段的 key 标识符 + params?: Record; // 模版型 i18n 文案的入参,JSDataType 指代传统 JS 值类型 +} +``` + +其中 `key` 对应协议 `i18n` 内容的语料键值,`params` 为语料为字符串模板时的变量内容。 + +假设协议已加入如下 i18n 内容: +```json +{ + "i18n": { + "zh-CN": { + "i18n-jwg27yo4": "你好", + "i18n-jwg27yo3": "${name}博士" + }, + "en-US": { + "i18n-jwg27yo4": "Hello", + "i18n-jwg27yo3": "Doctor ${name}" + } + } +} +``` + +**国际化多语言类型**简单范例: + +```json +{ + "type": "i18n", + "key": "i18n-jwg27yo4" +} +``` + +**国际化多语言类型**模板范例: + +```json +{ + "type": "i18n", + "key": "i18n-jwg27yo3", + "params": { + "name": "Strange" + } +} +``` + +描述举例: + +```json +{ + "componentName": "Button", + "props": { + "text": { + "type": "i18n", + "key": "i18n-jwg27yo4" + } + } +} +``` + + +### 2.3.5 上下文 API 描述(A) + +在上述**事件类型描述**和**变量类型描述**中,在函数或 JS 表达式内,均可以通过 **this** 对象获取当前组件所在容器(React Class)的实例化对象,在搭建场景下的渲染模块和出码模块实现上,统一约定了该实例化 **this** 对象下所挂载的最小 API 集合,以保障搭建协议具备有一致的**数据流**和**事件上下文**。  + +#### 2.3.5.1 容器 API: + +| 参数 | 说明 | 类型 | 备注 | +| ----------------------------------- | --------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------- | +| **this {}** | 当前区块容器的实例对象 | Class Instance | - | +| *this*.state | 三种容器实例的数据对象 state | Object | - | +| *this*.setState(newState, callback) | 三种容器实例更新数据的方法 | Function | 这个 setState 通常会异步执行,详见下文 [setState](#setstate) | +| *this*.customMethod() | 三种容器实例的自定义方法 | Function | - | +| *this*.dataSourceMap {} | 三种容器实例的数据源对象 Map | Object | 单个请求的 id 为 key, value 详见下文 [DataSourceMapItem 结构描述](#datasourcemapitem-结构描述) | +| *this*.reloadDataSource() | 三种容器实例的初始化异步数据请求重载 | Function | 返回 \ | +| **this.page {}** | 当前页面容器的实例对象 | Class Instance | | +| *this.page*.props | 读取页面路由,参数等相关信息 | Object | query 查询参数 { key: value } 形式;path 路径;uri 页面唯一标识;其它扩展字段 | +| *this.page*.xxx | 继承 this 对象所有 API | | 此处 `xxx` 代指 `this.page` 中的其他 API | +| **this.component {}** | 当前低代码业务组件容器的实例对象 | Class Instance | | +| *this.component*.props | 读取低代码业务组件容器的外部传入的 props | Object | | +| *this.component*.xxx | 继承 this 对象所有 API | | 此处 `xxx` 代指 `this.component` 中的其他 API | +| **this.$(ref)** | 获取组件的引用(单个) | Component Instance | `ref` 对应组件上配置的 `ref` 属性,用于唯一标识一个组件;若有同名的,则会返回第一个匹配的。 | +| **this.$$(ref)** | 获取组件的引用(所有同名的) | Array of Component Instances | `ref` 对应组件上配置的 `ref` 属性,用于唯一标识一个组件;总是返回一个数组,里面是所有匹配 `ref` 的组件的引用。 | + +##### setState + +`setState()` 将对容器 `state` 的更改排入队列,并通知低代码引擎需要使用更新后的 `state` 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式。 + +请将 `setState()` 视为请求而不是立即更新组件的命令。为了更好的感知性能,低代码引擎会延迟调用它,然后通过一次传递更新多个组件。低代码引擎并不会保证 state 的变更会立即生效。 + +`setState()` 并不总是立即更新组件, 它会批量推迟更新。这使得在调用 `setState()` 后立即读取 `this.state` 成为了隐患。为了消除隐患,请使用 `setState` 的回调函数(`setState(updater, callback)`),`callback` 将在应用更新后触发。即,如下例所示: + +```js +this.setState(newState, () => { + // 在这里更新已经生效了 + // 可以通过 this.state 拿到更新后的状态 + console.log(this.state); +}); + +// ⚠注意:这里拿到的并不是更新后的状态,这里还是之前的状态 +console.log(this.state); +``` + +如需基于之前的 `state` 来设置当前的 `state`,则可以将传递一个 `updater` 函数:`(state, props) => newState`,例如: + +```js +this.setState((prevState) => ({ count: prevState.count + 1 })); +``` + +为了方便更新部分状态,`setState` 会将 `newState` 浅合并到新的 `state` 上。 + + +##### DataSourceMapItem 结构描述 + +| 参数 | 说明 | 类型 | 备注 | +| ------------ | -------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | +| load(params) | 调用单个数据源 | Function | 当前参数 params 会替换 [ComponentDataSourceItemOptions 对象描述](#2315-componentdatasourceitemoptions-对象描述)中的 params 内容 | +| status | 获取单个数据源上次请求状态 | String | loading、loaded、error、init | +| data | 获取上次请求成功后的数据 | Any | | +| error | 获取上次请求失败的错误对象 | Error 对象 | | + +备注: 如果组件没有在区块容器内,而是直接在页面内,那么 `this === this.page` + + +#### 2.3.5.2 循环数据 API + +获取在循环场景下的数据对象。举例:上层组件设置了 loop 循环数据,且设置了 `loopArgs:["item", "index"]`,当前组件的属性表达式或绑定的事件函数中,可以通过 this 上下文获取所在循环的数据环境;默认值为 `['item','index']` ,如有多层循环,需要自定义不同 loopArgs,同样通过 `this[自定义循环别名]` 获取对应的循环数据和序号; + + +| 参数 | 说明 | 类型 | 可选值 | +| ---------- | --------------------------------- | ------ | ------ | +| this.item | 获取当前 index 对应的循环体数据; | Any | - | +| this.index | 当前物料在循环体中的 index | Number | - | + +## 2.5 工具类扩展描述(AA) + +用于描述物料开发过程中,自定义扩展或引入的第三方工具类(例如:lodash 及 moment),增强搭建基础协议的扩展性,提供通用的工具类方法的配置方案及调用 API。 + +| 参数 | 说明 | 类型 | 支持变量 | 默认值 | +| ------------------ | ------------------ | ---------------------------------------------------------------------------------------------------------------- | -------- | ------ | +| utils[] | 工具类扩展映射关系 | Array\<**UtilItem**\> | - | | +| *UtilItem*.name | 工具类扩展项名称 | String | - | | +| *UtilItem*.type | 工具类扩展项类型 | 枚举, `'npm'` (代表公网 npm 类型) / `'tnpm'` (代表阿里巴巴内部 npm 类型) / `'function'` (代表 Javascript 函数类型) | - | | +| *UtilItem*.content | 工具类扩展项内容 | [ComponentMap 类型](#22-组件映射关系 a) 或 [JSFunction](#2432事件函数类型 a) | - | | + +描述示例: + +```javascript +{ + 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: '@alifd/next', + version: '0.0.1', + exportName: 'Moment', + subName: '', + destructuring: true, + main: '' + } + }, { + name: 'recordEvent', + type: 'function', + content: { + type: 'JSFunction', + value: "function(logkey, gmkey, gokey, reqMethod) {\n goldlog.record('/xxx.event.' + logkey, gmkey, gokey, reqMethod);\n}" + } + }] +} +``` + +出码结果: + +```javascript +import clone from 'lodash/lib/clone'; +import { Moment } from '@alifd/next'; + +export const recordEvent = function(logkey, gmkey, gokey, reqMethod) { + goldlog.record('/xxx.event.' + logkey, gmkey, gokey, reqMethod); +} + +... +``` + +扩展的工具类,用户可以通过统一的上下文 this.utils 方法获取所有扩展的工具类或自定义函数 ,例如:this.utils.moment、this.utils.clone。搭建协议中的使用方式如下所示: + +```javascript +{ + componentName: 'Div', + props: { + onClick: { + type: 'JSFunction, + value: 'function(){ this.utils.clone(this.state.data); }' + } + } +} +``` + +## 2.6 国际化多语言支持(AA) + +协议中用于描述国际化语料和组件引用国际化语料的规范,遵循集团国际化中台关于国际化语料规范定义。 + + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ---- | -------------- | ------ | ------ | ------ | +| i18n | 国际化语料信息 | Object | - | null | + + +描述示例: + +```json +{ + "i18n": { + "zh-CN": { + "i18n-jwg27yo4": "你好", + "i18n-jwg27yo3": "中国" + }, + "en-US": { + "i18n-jwg27yo4": "Hello", + "i18n-jwg27yo3": "China" + } + } +} +``` + +使用举例: + +```json +{ + "componentName": "Button", + "props": { + "text": { + "type": "i18n", + "key": "i18n-jwg27yo4" + } + } +} +``` + +```json +{ + "componentName": "Button", + "props": { + "text": "按钮", + "onClick": { + "type": "JSFunction", + "value": "function() {\ + console.log(this.i18n('i18n-jwg27yo4'));\ + }" + } + } +} +``` + +使用举例(已废弃) +```json +{ + "componentName": "Button", + "props": { + "text": { + "type": "JSExpression", + "value": "this.i18n['i18n-jwg27yo4']" + } + } +} +``` + +# 3 应用描述 + +面向开发者的,描述完整应用的 Schema 规范,用于规范化约束**低代码平台**对**完整应用**的**输出**,以及**出码模块**( Schema2Code) 或**运行时动态渲染框架**(预览)的**输入**。 + +## 3.1 结构描述 + +- version { String } 当前应用协议版本号 +- componentsMap { Array } 当前应用所有组件映射关系 +- componentsTree { Array } 描述应用所有页面、低代码组件的组件树 +- utils { Array } 应用范围内的全局自定义函数或第三方工具类扩展 +- css { string } 应用范围内的全局样式; +- config: { Object } 当前应用配置信息 +- meta: { Object } 当前应用元数据信息 +- dataSource: { Array } 当前应用的公共数据源 (待定) +- i18n { Object } 国际化语料 + + +完整应用描述举例: + +```json +{ + "version": "1.0.0", // 当前协议版本号 + "componentsMap": [{ // 依赖 npm 组件描述 + "componentName": "Button", + "package": "alife/next", + "version": "1.0.0", + "destructuring": true, + "exportName": "Select", + "subName": "Button" + }], + "componentsTree": [{ // 应用内页面、低代码组件描述 + "componentName": "Page", // 单个页面 + "fileName": "page_index", + "props": {}, + "css": "body {font-size: 12px;} .table { width: 100px;}", + "meta": { // 页面元信息 + "title": "首页", // 页面标题描述 + "router": "/", // 页面路由 + "spmb": "abef21", // spm B 位 + "url": "https://fusion.design", // 页面访问地址 + "creator": "xxx", + "gmt_create": "2020-02-11 00:00:00", // 创建时间 + "gmt_modified": "2020-02-11 00:00:00", // 修改时间 + ... + }, + "children": [{ + "componentName": "Div", + "props": { + "className": "red", + }, + "children": [{ + "componentName": "Button", + "props": { + "type": "primary", + "valueBind": { // 变量绑定 + "type": "JSExpression", + "value": "this.state.user.name" + }, + "onClick": { // 动作绑定 + "type": "JSExpression", + "value": "function(e) { console.log(e.target.innerText) }", + } + }, + }] + }, { + "componentName": "Component", // 单个组件 + "fileName": "BasicLayout", // 组件名称 + "props": {}, + "css": "body {font-size: 12px;} .table { width: 100px;}", + "meta": { // 组件元信息 + "title": "导航组件", // 组件中文标题 + "description": "这是一个导航类组件...", // 组件描述 + "creator": "xxx", + "gmt_create": "2020-02-11 00:00:00", // 创建时间 + "gmt_modified": "2020-02-11 00:00:00", // 修改时间 + ... + }, + "children": [{ + "componentName": "Nav", + "props": { + "className": "red" + }, + "children": [{ + "componentName": "NavItem", + "props": {} + }] + }] + }] + }], + "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": "JSFunction", + "value": "function(){\n ... \n}" + } + }], + "css": "body {font-size: 12px;} .table { width: 100px;}", + "config": { // 当前应用配置信息 + "sdkVersion": "1.0.3", // 渲染模块版本 + "historyMode": "hash", // 浏览器路由:browser 哈希路由:hash + "container": "J_Container", + "layout": { + "componentName": "BasicLayout", + "props": { + "logo": "...", + "name": "测试网站" + }, + }, + "theme": { + // for Fusion use dpl defined + "package": "@alife/theme-fusion", + "version": "^0.1.0", + // for Antd use variable + "primary": "#ff9966" + } + }, + "meta": { // 应用元数据信息 + "name": "demo 应用", // 应用中文名称, + "git_group": "appGroup", // 应用对应 git 分组名 + "project_name": "app_demo", // 应用对应 git 的 project 名称 + "description": "这是一个测试应用", // 应用描述 + "spma": "spa23d", // 应用 spma A 位信息 + "gmt_create": "2020-02-11 00:00:00", // 创建时间 + "gmt_modified": "2020-02-11 00:00:00", // 修改时间 + ... + }, + "i18n": { + "zh-CN": { + "i18n-jwg27yo4": "你好", + "i18n-jwg27yo3": "中国" + }, + "en-US": { + "i18n-jwg27yo4": "Hello", + "i18n-jwg27yo3": "China" + } + } +} +``` + +## 3.2 文件目录 + +以下是推荐的应用目录结构,与标准源码 build-scripts 对齐,这里的目录结构是帮助理解应用级协议的设计,不做强约束 + +```html +├── META/ # 低代码元数据信息,用于多分支冲突解决、数据回滚等功能 +├── public/ # 静态文件,构建时会 copy 到 build/ 目录 +│ ├── index.html # 应用入口 HTML +│ └── favicon.png # Favicon +├── src/ +│ ├── components/ # 应用内的低代码业务组件 +│ │ └── guide-component/ +│ │ ├── index.js # 组件入口 +│ │ ├── components.js # 组件依赖的其他组件 +│ │ ├── schema.js # schema 描述 +│ │ └── index.scss # css 样式 +│ ├── pages/ # 页面 +│ │ └── home/ # Home 页面 +│ │ ├── index.js # 页面入口 +│ │ └── index.scss # css 样式 +│ ├── layouts/ +│ │ └── basic-layout/ # layout 组件名称 +│ │ ├── index.js # layout 入口 +│ │ ├── components.js # layout 组件依赖的其他组件 +│ │ ├── schema.js # layout schema 描述 +│ │ └── index.scss # layout css 样式 +│ ├── config/ # 配置信息 +│ │ ├── components.js # 应用上下文所有组件 +│ │ ├── routes.js # 页面路由列表 +│ │ └── app.js # 应用配置文件 +│ ├── utils/ # 工具库 +│ │ └── index.js # 应用第三方扩展函数 +│ ├── locales/ # [可选]国际化资源 +│ │ ├── en-US +│ │ └── zh-CN +│ ├── global.scss # 全局样式 +│ └── index.jsx # 应用入口脚本, 依赖 config/routes.js 的路由配置动态生成路由; +├── webpack.config.js # 项目工程配置,包含插件配置及自定义 webpack 配置等 +├── README.md +├── package.json +├── .editorconfig +├── .eslintignore +├── .eslintrc.js +├── .gitignore +├── .stylelintignore +└── .stylelintrc.js +``` + +## 3.3 应用级别 APIs +> 下文中 `xxx` 代指任意 API +### 3.3.1 路由 Router API + - this.location.`xxx` + - this.history.`xxx` + - this.match.`xxx` + +### 3.3.2 应用级别的公共函数或第三方扩展 + - this.utils.`xxx` + +### 3.3.3 国际化相关 API +| API | 函数签名 | 说明 | +| -------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------ | +| this.i18n | (i18nKey: string, params?: { [paramName: string]: string; }) => string | i18nKey 是语料的标识符,params 可选,是用来做模版字符串替换的。返回语料字符串 | +| this.getLocale | () => string | 返回当前环境语言 code | +| this.setLocale | (locale: string) => void | 设置当前环境语言 code | + +**使用范例:** +```json +{ + "componentsTree": [{ + "componentName": "Page", + "fileName": "Page1", + "props": {}, + "children": [{ + "componentName": "Div", + "props": {}, + "children": [{ + "componentName": "Button", + "props": { + "children": { + "type": "JSExpression", + "value": "this.i18n('i18n-hello')" + }, + "onClick": { + "type": "JSFunction", + "value": "function () { this.setLocale('en-US'); }" + } + }, + }, { + "componentName": "Button", + "props": { + "children": { + "type": "JSExpression", + "value": "this.i18n('i18n-chicken', { count: this.state.count })" + }, + }, + }] + }], + }], + "i18n": { + "zh-CN": { + "i18n-hello": "你好", + "i18n-chicken": "我有${count}只鸡" + }, + "en-US": { + "i18n-hello": "Hello", + "i18n-chicken": "I have ${count} chicken" + } + } +} +``` diff --git a/specs/material-spec.md b/specs/material-spec.md new file mode 100644 index 000000000..790063e7a --- /dev/null +++ b/specs/material-spec.md @@ -0,0 +1,1821 @@ +# 《低代码引擎物料协议规范》 + +# 1 介绍 + +## 1.1 本协议规范涉及的问题域 + +- 定义本协议版本号规范 +- 定义本协议中每个子规范需要被支持的 Level +- 定义中后台物料目录规范(A) +- 定义中后台物料 API 规范(A) +- 定义中后台物料入库规范(A) +- 定义中后台物料国际化多语言支持规范(AA) +- 定义中后台物料主题配置规范(AAA) +- 定义中后台物料无障碍访问规范(AAA) + + +## 1.2 协议草案起草人 + +- 撰写:九神、大果、元彦、戊子、林熠、屹凡、金禅 +- 审阅:潕量、月飞、康为、力皓、荣彬、暁仙、度城、金禅、戊子、林熠、絮黎 + +## 1.3 版本号 + +1.0.0 + +## 1.4 协议版本号规范(A) + +本协议采用语义版本号,版本号格式为 `major.minor.patch` 的形式。 + +- major 是大版本号:用于发布不向下兼容的协议格式修改 +- minor 是小版本号:用于发布向下兼容的协议功能新增 +- patch 是补丁号:用于发布向下兼容的协议问题修正 + + +## 1.5 协议中子规范 Level 定义 + +| 规范等级 | 实现要求 | +| -------- | ---------------------------------------------------------------------------------- | +| A | 强制规范,必须实现;违反此类规范的协议描述数据将无法写入物料中心,不支持流通。 | +| AA | 推荐规范,推荐实现;遵守此类规范有助于业务未来的扩展性和跨团队合作研发效率的提升。 | +| AAA | 参考规范,根据业务场景实际诉求实现;是集团层面鼓励的技术实现引导。 | + +## 1.6 名词术语 +- **物料**:能够被沉淀下来直接使用的前端能力,一般表现为业务组件、区块、模板。 +- **业务组件(Business Component)**:业务领域内基于基础组件之上定义的组件,可能会包含特定业务域的交互或者是业务数据,对外仅暴露可配置的属性,且必须发布到公域(如阿里 NPM);在同一个业务域内可以流通,但不需要确保可以跨业务域复用。 + - **低代码业务组件(Low-Code Business Component)**:通过低代码编辑器搭建而来,有别于源码开发的业务组件,属于业务组件中的一种类型,遵循业务组件的定义;同时低代码业务组件还可以通过低代码编辑器继续多次编辑。 +- **区块(Block)**:通过低代码搭建的方式,将一系列业务组件、布局组件进行嵌套组合而成,不对外提供可配置的属性。可通过区块容器组件的包裹,实现区块内部具备有完整的样式、事件、生命周期管理、状态管理、数据流转机制。能独立存在和运行,可通过复制 schema 实现跨页面、跨应用的快速复用,保障功能和数据的正常。 +- **模板(Template)**:特定垂直业务领域内的业务组件、区块可组合为单个页面,或者是再配合路由组合为多个页面集,统称为模板。 + +## 1.7 物料规范背景 +目前集团业务融合频繁,而物料规范的不统一给业务融合带来额外的高成本,另一方面集团各个 BU 的前端物料也存在不同程度的重复建设。我们期望通过集团层面的物料通不阻碍业务融合的发展,同时通过集团层面的物料流通来提升物料丰富度,通过丰富物料的复用来提效中后台系统研发,同时也能给新业务场景提供高质量的启动物料。 + +## 1.8 物料规范定义 + +- **源码物料规范**:一套面向开发者的目录规范,用于规范化约束开发过程中的代码、文档、接口规范,以方便物料在集团内的流通。 +- **搭建物料规范**:一套面向开发者的 Schema 规范,用于规范化约束开发过程中的代码、文档、接口规范,以方便物料在集团内的流通。 + +# 2. 物料规范 - 业务组件规范 + +## 2.1 源码规范 + +### 2.1.1 目录规范(A) + + +``` +component // 组件名称, 比如 biz-button + ├── build // 【编译生成】【必选】 + │ └── index.html // 【编译生成】【必选】可直接预览文件 + ├── lib // 【编译生成】【必选】 + │ ├── index.js // 【编译生成】【必选】js 入口文件 + │ ├── index.scss // 【编译生成】【必选】css 入口文件 + │ └── style.js // 【编译生成】【必选】js 版本 css 入口文件,方便去重 + ├── demo // 【必选】组件文档目录,可以有多个 md 文件 + │ └── basic.md // 【必选】组件文档示例,用于生成组件开发预览,以及生成组件文档 + ├── src // 【必选】组件源码 + │ ├── index.js // 【必选】组件出口文件 + │ └── index.scss // 【必选】仅包含组件自身样式的源码文件 + ├── README.md // 【必选】组件说明及 API + └── package.json // 【必选】组件 package.json +``` + + +#### README.md + +- README.md 应该包含业务组件的源信息、使用说明以及 API,示例如下: + +``` +# 按钮 // 这一行是标题 + +按钮用于开始一个即时操作。 // 这一行是描述 + +{这段通过工程能力自动注入, 开发者无需编写 +## 安装方法 +npm install @alifd/ice-layout -S +} + +## API + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ---- | ---- | ------ | ------------------- | ------ | +| type | 类型 | String | `primary`、`normal` | `normal` | +``` + + + +- README.en-US.md(文件命名采取 [bcp47 规范](http://www.rfc-editor.org/rfc/bcp/bcp47.txt))多语言的情况,可选 + +``` +# Button + +Button use to trigger an action. + +{这段通过工程能力自动注入, 开发者无需编写 +## Install +npm install @alifd/ice-layout -S +} + +## API + +| Param | Description | Type | Enum | Default | +| ----- | ----------- | ------ | ------------------- | ------- | +| type | type | String | `primray`、`normal` | normal | +``` + +#### package.json +`package.json` 中包含了一些依赖信息和配置信息,示例如下: + +```json +{ + "name": "@alife/1688-button", + "description": "业务组件描述", + "version": "0.0.1", + "main": "lib/index.js", + "stylePath": "lib/style.js", // 【私有字段】样式文件地址,webpack 插件引用 + "files": [ + "demo/", + "lib/", + "build/" // 存放编译后的 demo,发布前应该编译生成该目录 + ], + "dependencies": { + "@alifd/next": "1.x" // 【可选】可以是一个 util 类型的组件,如果依赖 next,请务必写语义化版本号,不要写*这种 + }, + "devDependencies": { + "react": "^16.5.0", + "react-dom": "^16.5.0" + }, + "peerDependencies": { + "react": "^16.5.0" + }, + "componentConfig": { // 【私有字段】组件配置信息 + "name": "button", // 组件英文名 + "title": "按钮", // 组件中文名 + "category": "form" // 组件分类 + } +} +``` + +#### src/index.js + +包含组件的出口文件,示例如下: + +```javascript +import Button from './Button.jsx'; +import ButtonGroup from './ButtonGroup.jsx'; + +export const Group = ButtonGroup; // 子组件推荐写法 + +export default Button; +``` + +推荐用法 + +```javascript +import Button, { Group } form '@scope/button'; +``` + +#### src/index.scss + +```css +/* 不引入依赖组件的样式,比如组件 import { Button } from '@alifd/next'; */ +/* 不需要在 index.scss 中引入 @import '~@alifd/next/lib/button/index.scss'; */ + +/* 如果需要引入主题变量引入此段 */ +@import '~@alifd/next/variables.scss'; + +/* 组件自身样式 */ +.custom-component { + color: $color-brand1-1; +} +``` + +#### demo +demo 目录存放的是组件的文档,无文档的业务组件无法带来任何价值,因此 demo 是必选项。demo 目录下的文件采取 markdown 的写法,可以是多个文件,示例(demo/basic.md)如下: + +demo/basic.md + +~~~ +--- +title: {按钮类型} +order: {文档的排序,数字,0 最小,从小到大排序} +--- + +按钮有三种视觉层次:主按钮、次按钮、普通按钮。不同的类型可以用来区别按钮的重要程度。 + +:::lang=en-US +--- +title: Container +order: 3 +--- + +Change the default container by passing a function to `container`; +enable `useAbsolute` to use `absolute position` to implement affix component; + +::: + +```jsx // 以下建议用英文编写 +import Button from '@alife/1688-button'; + +ReactDOM.render(
+ +
, mountNode); +``` + +```css +.test { + background: #CCC; +} +``` +~~~ + +### 2.1.2 API 规范(A) + +API 是组件的属性解释,给开发者作为组件属性配置的参考。为了保持 API 的一致性,我们制定这个 API 命名规范。对于业界通用的,约定俗成的命名,我们遵循社区的约定。对于业界有多种规则难以确定的,我们确定其中一种,大家共同遵守。 + +#### 通用规则 + +- 所有的 API 采用小驼峰的书写规则,如 `onChange`、`direction`、`defaultVisible`。 +- 标签名采用大驼峰书写规则,如 `Menu`、`Slider`、`DatePicker`。 + +#### 通用命名 + +| API 名称 | 类型 | 描述 | 常见变量 | +| :------------- | :------------- | :----------------------------------------------------------- | :---------------------------------------------------- | +| shape | string | 形状,从组件的外形来看有区别的时候,使用 shape | | +| direction | enum | 方向,取值采用缩写的方式。 | hoz(水平), ver(垂直) | +| align | enum | 对齐方式 | tl, tc, tr, cl, cc, cr, bl, bc, br | +| status | enum | 状态 | normal, success, error, warning | +| size | enum | 大小 | small, medium, large 更大或更小可用(xxs, xs, xl, xxl) | +| type | enum or string | 分类:1. dom 结构不变、只有皮肤的变化 2.组件类型只有并列的几类 | normal, primary, secondary | +| visible | boolean | 是否显示 | | +| defaultVisible | boolean | 是否显示(非受控) | | +| disabled | boolean | 禁用组件 | | +| closable | bool/string | 允许关闭的方式 | | +| htmlType | string | 当原生组件与 Fusion 组件的 type 产生冲突时,原生组件使用 `htmlType` | | +| link | string | 链接 | | +| dataSource | array | 列表数据源 | [{label, value}, {label, value}] | +| has+'属性' | boolean | 拥有某个属性 | 例如 `hasArrow`, `hasHeader`, `hasClose` 等等 | + + +#### 多选枚举 + +当某个 API 的接口,允许用户指定多个枚举值的时候,我们把这个接口定义为多选枚举。一个很典型的例子是某个弹层组件的 `closable` 属性,我们会允许:键盘 esc 按键、点击 mask、点击 close 按钮、点击组件以外的任何区域进行关闭。 + +不要有一个 API 值,支持多种类型。例如某个弹层的组件,我们会允许 esc、点击 mask、点击 close 按钮等进行关闭。此时 API 设计可以通过多个 API 承载,例如: + +```js +closable?: boolean; // 默认为 true +closeMode?: CM[] | string; // 默认值是 ['close', 'mask', 'esc'] +``` + +true 表示触发规则都会关闭,false 表示触发规则不会关闭。 + +示例: + +- ``,所有合法条件都会关闭 +- ``,任何情况下都不关闭,只能通过受控设置 visible +- ``,用户按 esc 或者点击关闭按钮会关闭 + +#### 事件 + +- 标准事件或者自定义的符合 w3c 标准的事件,命名必须 on 开头, 即 `on` + 事件名,如 onExpand。 + +#### 表单规范 + +- 支持[受控模式](https://reactjs.org/docs/forms.html#controlled-components)(value + onChange) (A) + - value 控制组件数据展现 + - onChange 组件发生变化时候的回调函数(第一个参数可以给到 value) +- `value={undefined}` 的时候清空数据, field 的 reset 函数会给所有组件下发 undefined 数据 (AA) +- 一次完整操作抛一次 onChange 事件 `建议` 比如有 Process 表示进展中的状态,建议增加 API `onProcess`;如果有 Start 表示启动状态,建议增加 API `onStart`  (AA) + +#### 属性的传递 +**1. 原子组件(Atomic Component)** +> 最小粒子,不能再拆分的组件 + +举例:Input/Button/NumberPicker + +期望使用起来像普通的 html 标签一样,能够把用户传入的参数,透传到真正的节点上。 + +```jsx + +``` + +渲染后的 dom 结构: + +```jsx + + + +``` + +**2. 复合组件(Composite component)** + +复合组件一般由两个及以上的原子组件/复合组件构成,比如:Select 由 Inupt + 弹窗组成,Search 由 Select + Button 组成,TreeSelect 由 Tree + Select 组成。 + +为了提高组件使用的便利性,对 API 属性的要求如下: +1. 复合组件核心的原子组件(比如 Search 的核心原子组件是 Input)的属性以及使用频率高的属性建议扁平化,让复合组件可以直接使用其属性; +2. 复合组件内的非核心原子组件,则通过 `xxxProps` (如 inputProps/btnProps)的方式,将参数传递到相应原子组件上。 + + +**属性扁平化例子**: + +比如 `Search` 组件由 `Input` 和 `Button` 构成,但是 `Search` 更像是 `Input` ,因此把 `Input` 作为主要组件,将属性扁平化。即在 `Search` 组件上直接使用一些 `Input` 的属性。 `` + +比如 `Select` `TreeSelect` 都有弹层部分,`Overlay` `Overlay.Popup` 的 `visible` 属性使用率较高,一般用于 fixed 布局下的弹窗滚动跟随。因此把该属性暴露到最外层,简化使用 `