企业级应用搭建平台是如何设计资产体系的?-阿里云开发者社区

开发者社区> AlibabaF2E> 正文
登录阅读全文

企业级应用搭建平台是如何设计资产体系的?

简介: 看看云凤蝶的资产体系设计。
作者 | 绯一

image.png

搭建系统行业现状

面向领域提供解决方案,提高生产效率

image.png
via 云凤蝶可视化搭建的推导与实现

搭建系统并不是一个稀奇的概念,从 Dreamweaver 开始,有大量的产品试图用装配式的开发解决应用生产的效率问题。它们面向特定场景做抽象、沉淀出最佳实践,再通过产品封装来加速整个制作过程。

比如 Dreamweaver 认为 HTML 树是不重要的,应该通过所见即所得的自由拖拽生成;比如 SwiftUI 认为移动场景下,数据的流向与更新应该被高度简化,于是提供简洁的交互来快速建立组件与模型间响应式的数据流。

搭建系统要解决什么问题?

一言以蔽之,缺人。

前端沉重的上手门槛导致招聘培养非常困难,前端资源逐渐成为项目瓶颈,无法跟上爆发式的业务增长。现场我做了一个调查,写过前端页面的人中只有 1/3 是专职前端,剩下的大家都是被逼的没办法:服务早就写好了,但没前端资源,只能撸起袖子自己把页面做了。

image.png
via How it feels to learn JavaScript in 2016

如何解决问题?

参考制造业的最佳实践

作为高精尖产品 iPhone,通过标准化手机的制作程序,将大量生产外包。苹果有 200 多个零部件供应商,400 多道组装工序,终端的富士康郑州工厂每天可以生产 50 万部 iPhone,零部件拆分和流水线的组装大大提升产能,苹果通过这种方式书写了普通工人批量制造高精尖产品的神话。

image.png
iPhone 的供应链,红框内是富士康 via 云凤蝶中台研发提效实践

汽车行业将整车拆分为零部件,外包给专业的零部件生产商,再通过流水线的组装完成最后一步。这些组装汽车的人,可能对汽车一窍不通,但只需要在流水线,按部就班的重复再重复就行了。

image.png
传统研发 vs 搭建研发 via 云凤蝶中台研发提效实践

还有我们所熟悉的每一个快餐店:

image.png
论如何制作汉堡包

通过上面几种抽象,传统的生产过程被重新解构为:

  • 擅长生产零部件的沉淀为领域专家:比如相机、螺丝、轮胎、面包和炸鸡,在领域内不断突破降低成本
  • 在终端门槛极低的流水线组装:可以招聘大量普通工人扩大生产,实现效率的不断提升

云凤蝶的实践经验

企业级应用制作平台

在如今的 Web 领域,无论是大公司还是小公司,都很少会从 span、div 标签写起,大家都会有自己的一套组件库,对于 Web 研发我们似乎也可以使用这样的思路来解决问题。

在蚂蚁集团,有 36% 的中后台应用以这种方式搭建,应用的平均复杂度在 20 个页面,上百个组件的量级。我们通过对中后台应用的抽象,结合设计规范,希望能重新定义应用的制作方式,解决三个问题:

  • 降门槛:非专业前端也能搭建,拓展新的角色在终端「组装汽车」,可以大量外包用工
  • 提效率:结合设计规范,简化应用制作的各种概念,比如样式、布局、数据流、网络请求
  • 60 分:很多中后台应用缺乏产品和设计,体验参差不齐。之前 Ant Design 的作者曾提过一个观点,antd 最大的价值是保证应用可以达到一个最低及格分

自然搭建系统在发展中也会面临各种问题,本文主要结合在云凤蝶的实践经验,聊一聊对于一个搭建系统来说,如何通过组件化的思路应对搭建系统发展上面临的各种挑战。

一个搭建系统要面临怎样的挑战?

支持的业务复杂度提升,搭建系统的复杂度也会提升

每个人应该都写过最朴素的搭建系统,它的形式类似一个营销搭建系统。在业务开发中,我们会把页面中经常变化的内容抽象为模版的配置项,并在模版外进行配置,每当配置变化,模版无需重新构建和验证即可快速迭代,此时一个应用由一个巨大的模版组成,如下图 1。

随着需求复杂度的提升,搭建系统需要做更细粒度的拆分。比如双 11 有多个活动页面,页面间有一些共享模块,比如签到和抽奖。从复用和维护性的角度,我们会面向功能封装出一些业务组件,此时搭建系统也相应要进化为楼层式搭建,对业务组件做二次编排和配置。

原本的一个配置项变为配置项数组,按照顺序描述每个组件暴露的接口,此时的搭建系统已经能够承载一定对复用的需求,有了组件化的雏形,如下图 2。

image.png
搭建系统的演进过程

再往后发展,页面出现了更自由的组合关系:导航栏、父子嵌套、弹窗、表单表格,这是典型的中后台应用。我们必须进一步拆分,并且多数组件已经无法针对具体应用重新开发,必须沉淀更细粒度的通用型组件,把原本大量内置到模版的逻辑,拆出来表达为组件间关系,才能支撑业务的持续增长,如上图 3。

从最简单的营销搭建到中后台搭建,整个组件体系在技术上有很多挑战:

image.png

组件体系 1.0 - 铸型

要建立一个怎样的组件体系,才能应对这些挑战呢?

组件从哪里来?

首先要回答一个问题,去哪找组件?

NPM 是社区非常有生命力的组件中心,复用 NPM 的基础设施是一个理想的选择:

  • 重新造一套组件的成本太高了,Ant Design 一个组件的平均成本在一个月左右
  • NPM 上有大量的高质量组件,可以快速补充组件内容
  • 不同的业务会有自己的一套组件库,无法全部内置到平台,必须要「开放」
  • 当业务出现自定义需求,可以封装 NPM 完成最后一公里

image.png
和 NPM 的关系

组件的生产必须与平台解耦,再通过组件规范建立连接,这样才能快速建立组件生态,完成面向组件的拓展。

1、对组件进行抽象和建模

建立组件规范:NPM 和搭建系统的连接

前面我们提到,对应用的配置可以分解对多个组件的配置。一个典型的组件编辑的场景如下,我们要能找到组件向外暴露的接口,并通过图形化的方式编辑他们。

image.png
编辑组件

再以主流的前端框架来看,一个组件是什么?

image.png
组件的抽象

UI 就是组件最终的渲染效果,f 是组件的实现,而 props 就是组件向外暴露的接口,以 React 的代码为例,上面的可视化编辑和下面的代码相对应:

<Button type="primary" loading={false} children="我是一个按钮" />

2、从类型出发找到组件

根据 NPM 的规范,我们可以从 package.json.typings 类型文件出发,通过 AST 解析找到所有的模块导出,并提取出其中符合 UI = F(props) 抽象的组件和属性定义。

比如在 React 技术栈下,React.ComponentReact.FCReact.PureComponent 都是符合要求的。

image.png
解析组件和 Props

3、识别/提取组件的元信息

找到组件后,我们可以深入挖掘他的元信息,比如如下的类型声明,我们很容易推测几件事情:

/**
 * @title 尺寸
 */
size: "small" | "middle" | "large";

/**
 * @format icon
 */
beforeIcon: string;

children?: React.ReactNode;
  • size 的中文描述是尺寸
  • size 只有三种取值,语义类似枚举,适合用下拉选择来编辑
  • beforeIcon 的类型是 string,但 formaticon,适合用图标选择器来编辑

将这些对元信息的推测规则沉淀下来,我们可以得到一个渲染引擎驱动的属性面板。在组件无需任何额外定义的情况下,尽可能的提升组件编辑效率。

4、一些复杂的编辑意图推断

除了上面对基础类型的简单推断,我们还可以做一些更深入的分析推断。

比如 nullable 通常用来表达可关闭的编辑意图。表格的分页属性可以配置为 Object 类型数据表达分页详细信息,也可以将值设为 false 来关闭整个分页功能,我们可以使用下面这种交互:

Table.pagination: false | Pagination

640 (2).gif
nullable

比如 unionType 通常用来表达分支情况。组件的提示信息有三种类型,每种类型都会一些特定配置项,在不同分支切换时,需要删除前一个分支的值,并为新的分支设置默认值。

tips: Text | Card | Popconfirm

640 (3).gif
unionType

组件如何加载、预览

应用如何处理 f 的依赖?

在写代码的模式里,我们把依赖安装到本地,再通过 Webpack 类似的工具对文件进行打包,每当代码修改/依赖发生变化,应用会重新构建,最终发布时,应用会把所有依赖的代码打包到一起。

但对于搭建系统来说,改一行文字等 5s 显然是不能接受的,我们要提供实时预览的方案。

image.png
一个简单的依赖关系

常见的玩法是,每个组件提前独立打 umd 包,应用只构建自己的代码,再通过 loader 远程加载所有外部组件依赖,形成一个 distMap,最终做组件渲染,这里 Map 的值就是上面我们提到的 UI = f(props)里的 f。

{
  Button: eval(request(buttonDist)),
  Card: eval(request(CardDist)),
}

React.createElement(map['Button'], ...);

但把所有依赖都打包进去的方法会导致 A 被重复打包多次,如上图,如果 A、B、C 分别打一个 umd 包,应用会有三个 A 的打包体积,并且对于 React.Context 等场景还会带来不同实例数据不通的问题。

我们需要有更细粒度的模块打包方式,能够支持按照版本规则对 A 进行复用。

1、Bundless 技术方案

NPM 包维度提前打包 & 实时依赖计算

组件在第一次进入到系统时,会按照依赖树递归的做 NPM 级打包,并将结果存储到 Assets CDN 上。

当前端应用的依赖发生变化时,通过请求 Assets CDN,按照版本合并 A & C 的所有依赖,并通过一次网络请求加载回来,再拆分给 loader 装载到 distMap 上。

image.png
组件导入 & 依赖加载

2、TreeShaking

上面的场景中,提前打包的粒度是 NPM 包级,这会导致一个问题,应用只使用了 lodash.get 但却加载了整个 lodash,体积还是很大。如下图,使用了 antdButtonMenuTable,最后加载了整个 antd

image.png
按需 TreeShaking

我们还需要做一些动态的处理,在应用发布时,根据应用对组件的实际使用情况,自动 TreeShaking 掉未使用的模块,创建一个虚拟的 antd 来降低体积。

这里有一些衍生的问题,如何保证依赖计算的速度、为什么 treeShaking 是安全的,以及为什么不做文件级的 Bundless?后续会有文章专门介绍。

组件体系 2.0 - 演进

「偷」来的组件不够好用怎么办?

通过一键导入 NPM 可以帮我们快速补充组件内容,但 NPM 上是相对松散的组件,距离在搭建系统上好用还有很大距离,我们需要对他们做一些封装,并且建立能持续迭代和治理的方案。

横向封装

1、弹窗类组件难以使用

弹窗类组件通常有一个受控属性来控制显示隐藏,如果设为可见,会挡住其他所有组件的编辑;如果设为不可见则无法编辑弹窗本身。

我们尝试抽象弹窗组件的特性,结合编辑态大纲树选中状态这一交互做一些封装:

  • 大纲树上选中 visible: true
  • 取消选择 visible: false

image.png

2、dataEntry 类组件双向绑定成本过高

输入框等组件的值也是受控模式,在输入框输入后需要手动在 onChange 方法里把新的值同步回 value 上,这使得表单类组件在搭建系统下使用效率很低。

我们同样去抽象这类组件的特征,在组件接入时,让组件回答几个问题:

  • 是否为表单类组件
  • 表达值的属性名是什么(比如 value)
  • 同步数据的事件是什么(比如 onChange)

这样我们可以建立一个虚拟的 store,在 onChange 事件触发时,自动完成事件参数到 value 的数据同步。在产品上的体现就是双向绑定,用户只需要为表单类绑定一个变量,数据同步就自动完成了。

image.png

3、面向特征做能力切面的拓展

类似上面两种的封装方式还有很多,这种面向组件特征而不是具体的组件做抽象,有几个非常明显的好处:

  • 横行提升表达力:此类组件都能使用,能力是有限的,但组件是无限的
  • 降低耦合:系统和组件解耦,通过能力描述建立关联
  • 统一心智:通用行为集中在与组件无关的配置区

image.png
组件表现力 = 组件数量 x 横向能力

举个例子,一个普通的头像组件,经过大量通用能力的描述可以变为带徽标的头像数组。

资产沉淀

除了用导入 NPM 的方式生产组件,我们还可以在搭建系统沉淀一些组件吗?

1、画布组件

如果一个应用中有多个地方都使用了相似的布局,我们可以把这部分内容提取为画布组件,并像 React 那样,向外暴露一些属性,实现一处维护、多处使用的目的。

如下,我们把用户信息的展示封装为一个组件,而用户信息作为外部参数传入,这种局部复用的思路和 NPM 是一致的,只是生产组件的方式不仅局限于写代码,还可以通过搭建。

image.png
封装画布组件

2、JSXBox 最后一公里

除了使用已有的组件进行搭建外,有些场景可能更适合用代码,比如根据数据动态嵌套渲染,或是绘制一个复杂的图表。我们可以在页面上挖一个洞,让用户写代码的方式,完成这部分定制化的需求。

它的形态有些类似 CodeSandbox,写一段 React 代码,最终和其他组件混合跑在一个页面上。

image.png
JSXBox

3、资产包

在持续发展中,每条业务线都会沉淀自己适用的业务资产,可能是一系列 NPM 包,也可能是我们可以提到的搭建系统内的资产。在产品形态上,我们可以引入资产大包的概念,业务线的开发可以聚合比如 UI 组件、工具函数、服务等,整合到资产包内,再发布给其他应用使用,通过这种方式完成业务资产沉淀和定向的二次效率提升。

并且无论是画布组件、代码组件、JSXBox 都是遵循同样一套组件规范,我们可以直接将搭建系统内沉淀的资产包发布到 NPM。

适合搭建的走搭建、适合代码的写代码。用户可以通过 NPM 完成各自的互相融合研发。

image.png
沉淀资产包

版本治理

只要有版本,就会有版本碎片

image.png
antd 的版本碎片分布 & antd4 changeLog

当搭建系统封装组件给应用使用时,必然会出现版本,也就必然面临版本碎片问题。版本碎片无论对组件的开发者还是使用者都是巨大的成本,对于搭建系统来说,如何解决这个问题?

一次 API 不兼容变更

我们来看一个具体的例子,Menu 早期用 Boolean 表达水平/垂直布局,但新的设计规范引入第三种布局,Boolean 无法承载,需要升级为 String 枚举,从 API 上来看,确实是一次不兼容升级。

image.png
Menu1.0 -> Menu2.0

但组件的 API 发生变化,能力并未发生变化,也就是 Menu2.0 仍然支持水平/垂直的布局能力,只是旧的 Boolean 数据不再适用新的组件实现,需要更新。

而搭建平台下,组件的使用是严格受控的,是一份结构化的数据,我们完全可以通过精准的 Codemod 来将所有旧版本的数据升级为新版本,即:

delete $props.vertical
create $props.layout: $before.vertical ? 'vertical' : 'horizontal'

通过这种方式,我们可以将大部分组件不兼容的版本升级上来,组件始终可以保持向前兼容,0 版本碎片。

分享一个数据,去年 antd 从 3 到 4 的升级,使用云凤蝶搭建的几百个应用全程无感知,只需要点一下升级等上一会就行了。

实现开放与收敛

有生命力的资产体系

image.png

通过上面各种手段,我们实现了一个无序的 NPM 组件到搭建系统的资产的过度,在这个过程:

  • 一键导入:让原来的组件都能用
  • 横向封装:让原来的组件在搭建系统下更好用
  • codemod:让用户始终用最新的组件

同时,面向组件规范、能力切面的抽象,使得在支撑更复杂业务的同时,搭建系统本身的复杂度可以得到收敛。通过底层的各种功能模块支撑,建立起一个有生命力的资产体系。

one more thing

如何评价程序员 50% 的时间都在写表单表格?

就算搭建系统再好用,资产再有生命力,用户仍然还是要一个表单项一个表单项配置表单、表格、高级搜索,处理双向绑定、字段映射等重复工作,面对有大量规律可循的增删改查中后台应用,我们能不能抽象一些套路,把程序员从千篇一律的工作中解脱出来呢?

实际上我们观察 API 文档可以发现,接口格式和最终的表单、表格有非常大的关联关系

  • GET /api/use 查询用户列表,大概率会使用表格,表格列和返回值有高度的对应关系
  • POST /api/user 新增用户信息,大概率会使用表单,表单项和入参数有高度的对应关系
  • name: string 大概率在表单中使用输入框,在表格中使用文本

这些推断来源于对 API 元信息的理解、按照能力切面为组件做的封装,以及中后台应用在设计上的最佳实践。

image.png
传统 vs 智能向导

有了智能向导的帮助,用户只需要选择接口、勾选他们想要的字段,再做一些简单的映射配置,即可生成带有完整逻辑功能的表单、表格页面,在云凤蝶应用中,有 53% 的等效代码由机器自动生成。

点击查看视频

写在最后

The dignity of movement of an ice-berg is due to only one-eighth of it being above water.
Ernest Hemingway

image.png

最后以一张经典冰山图作为结尾,实际上我们看到的很多酷炫功能,为了保障他的正常运行,在看得到的冰山下,有大量看不见的工具链路、渲染引擎、设计规则在悄悄运转。

作者:绯一 ,就职于蚂蚁集团体验技术部(AFX),专注于工程领域。曾参与组件规范、构建工具、应用框架、研发平台等基础设施的研发工作。目前主要负责低代码搭建平台的资产体系设计,致力于打造下一代 Web 研发平台「云凤蝶」。

image.png

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
AlibabaF2E
使用钉钉扫一扫加入圈子
+ 订阅

阿里经济体前端技术最新内容汇聚在此,由阿里经济体前端委员会官方运营。我们的愿景是建立全球一流的前端团队,链接商业,让数字世界触手可及是我们的使命。阿里经济体前端委员会致力于加强技术前瞻性、推进集体成长、提升国际影响力。同时我们运营着阿里经济体前端的官方公众号:Alibaba F2E,欢迎关注。

官方博客
最新文章
相关文章