likes
comments
collection
share

「面向未来」移动端 Web 组件库建设思考

作者站长头像
站长
· 阅读数 14

开篇感想

在 ChatGPT-4 发布后,用 AI 写 UI 已经不再是一种可能,而是一种选择了。未来我们的工作慢慢会从造轮子转变为审核轮子的过程。

在笔者看来,程序员的发展就像医生这门行业,越来越多的自动化仪器并不能取代经验丰富的医生,所以整个市场环境来讲,从追求性价比的人海战术(谁能比的过 AI 的性价比,它价格约等于 0)慢慢会转向追求高经验的优秀技术开发。同样,新人想入行也会变得越来越难 ...

那 AI 现在还取代不了的是什么?笔者认为是眼界,毕竟 AI 还没有学会上升维度进行思考。

那对于我们而言,如何能更高维度的思考问题,苟的住未来才是当下要紧的事。

那笔者作为一个以跨端开发为主的终端开发工程师,看 UI 组件的角度要更远一些,做一些 AI 还做不到的事。

背景

需求背景

稿定目前并没有一套成熟的移动端上的 Web 组件库,来支撑后续的 UI 开发、业务开发。

线上现有的 Wap 站,并没有形成一套可用的移动端组件,而且从各个角度来讲都不适合继续维护。

技术背景

由于一些历史债务,编辑器核心层现在停留在 Vue 2.7 上,而上层选型需要与内核框架一致,所以组件化选型只能在 Vue2 的开源 UI 组件库中进行选择。

关键背景:Vue2 2023.12.31 将会停止维护,届时都需要转向 Vue3。

维护一套基于 Vue2 的组件库,显然是会被历史所淘汰的,这不符合要我们面向未来的特征。

移动端组件库在原生侧还在构建一套基于 Flutter 的(Flutter 多引擎渲染,在稿定 App 的实践),这也会产生重复建设的问题。

思考

方案对比

基于 Flutter 的 Web 组件化之路

「全网首发」Flutter element embedding 初探(面向 Web 未来)

总结:

未来很有可能把 Flutter Web 产物作为一个 Web 组件放入到 Div 中渲染。

而原生就可以用 Flutter Native 产物作为一个 Native 组件由原生布局渲染。

达到一次开发,多端使用,性能与体验也可达到原生级。

但是,现在还不行,当下的 Flutter 开发版中只能指定一个endpoint, 那整个 HTML 中只能渲染一个 Flutter Div,这是不符合我们预期想要的。

等 Flutter Web 也支持了类似 App 多引擎方案后,整套 Flutter Web 组件化才能落地。

Semi 字节开源组件库

笔者深入了解下字节的这套 Semi 方案。

可以说它在设计上确实也是面向 Web 未来的。

「面向未来」移动端 Web 组件库建设思考

Semi Design 采用了一套跨前端框架技术方案,F/A 分层设计,将每个组件的 JavaScript 拆分为两部分:Foundation 和 Adapter,这使得我们可以通过仅重新实现适配器来跨框架重用 Foundation 代码,例如 React、Vue、Angular、Svelte 或者 WebComponent,快速打造不同平台上的通用组件库。

如同它说的那样,它的目的是打造跨框架的 UI 组件库,不再被 React、Vue 等框架束缚。

那我们可以选择它吗?

至少目前不行,Semi 其实离它的目标还很远,它现在只实现了基于 React 的桌面端 UI 组件库。

Vue Adapter 并没有实现(跟字节的前端选型有关,应该不会主动实现 Vue 版的,估计需要社区实现了)。

移动端 UI 组件库的话,看 issues 已经有官方回答,他们定位是中后台,暂时不会考虑移动端。

Vant2 有赞开源组件库

再结合我们需求背景来看,在Vue2 + 移动端前置条件下,口碑较好的就只有 Vant 组件库了。

而由于 Vue 版本的原因,我们也是用不了 Vant3、Vant4 版本的组件库,只能选择停止维护的 Vant2 组件库。

闲鱼 fish UI 闭源组件库

闲鱼大终端UI组件库——FishUI建设之路

随着闲鱼前端架构的不断演进,一些关键技术设施需要结合业务特征逐步自建,技术方案也要拥抱社区来提升可扩展性。一方面, 闲鱼跨端开发框架kun 让前端开发者使用JS/CSS/HTML即可交付终端页面,同时兼顾了动态性和高性能,另一方面,前端UI框架也正从集团 rax 逐步转向社区 React 方案。

在这个大背景下,围绕 kun 和 web 两个容器的跨端组件建设也势在必行,因此 Fish UI 应运而生,它将全面拥抱 react 生态,并借助 kun 容器的能力,为闲鱼终端开发者提供一套高易用性和稳定性的跨端(kun & web)UI组件库。

简单的说,它通过相同的代码定义,来保持 Web 容器和 kun 容器的统一,达到一次 Web 代码编写,达到跨端原生渲染的能力。

「面向未来」移动端 Web 组件库建设思考

它是符合未来趋势的,但问题是它不开源(就算开源也要斟酌,社区上对闲鱼的开源产品都是毁誉参半的)。

当然,通过文章中难点和挑战也可以看出,他们遇到的问题更多还是终端开发和前端开发理念不一致的沟通协作问题,需要大量的规范约束和工程能力支撑,比如 api 设计规范、交互视觉规范、代码开发规范、统一的脚手架能力、统一的 CI/CD 能力等,那我们本次在建设之前就要先抹平这一部分成本。

未来架构

那我们只能在项目中直接使用 Vant2 吗?

虽然现在开发可能更便捷更低成本,但那不仅不能适配后续的 Vue3 + Vant4 的改造,也不符合我们对于大一统组件化的未来设想。

那我们在当下这个阶段,要如何构造既能符合当下,又符合未来的组件架构?

面向定义而非面向实现

我们在做 跨端 UI 组件化 时,使用 Flutter 多引擎方案跨 iOS、Android 提供跨端 UI 组件库时,曾构建了一套 FGUIComponentAPI 来抹平各端调用实现差异。

我们使用 YAML 来定义组件的属性、方法、回调,用 codegen 的方式生成各端链接代码, 面向 API 编程的思想一直贯穿其中。

那 Flutter 组件库和 Web 组件库可以合一吗?

现在是很难,但我们可以按相同的标准来建设,后续就可以用codegen来抹平中间差异,达到未来可快速迁移实现的目的。

回到我们当下要建设的 Web 组件库,我们也应该采用相同的策略,面向抽象而不是面向实现。

具体的来说,

我们不要直接使用 Vant 的实现,而是在它之上再封装一个抽象层,再由 Adapter 层去实现 Vant 调用。

是不是很像上文说的 Semi 的方案?但我们抽象的方式并不大一样。

通过阅读源码 Semi 可以看出,它的 foundations 层设计,是为了样式控制,常量控制,公共逻辑。

而我们需要使用的是 Vant 的组件库(且后续也可能是 Flutter 组件库),样式控制,常量控制,公共逻辑都在它们之内自闭环,所以我们的 foundations 相当于是一份组件描述定义,描述组件名称、开放的属性、开放的方法。

Design-token

Design-token 是我们已构建的一套 Style 规范体系。

那在 Vant2 组件库上,也可以很简单的跟其配置相结合,让上层开发无需关心组件样式。 而未来 Flutter Web UI 加入后,Design-token 会构建于 Flutter 侧。

建设方案

设计规划

总体架构

「面向未来」移动端 Web 组件库建设思考

Foundation 基础层用于描述一个 UI 组件,用声明式 API,达成行为交互约定,由各自适配器端代码去实现这些约定。

Web UI Adapter 是基于 Web UI 的适配,采用 Vant2作为实现基础。

Flutter UI Adapter 后续会使用我们持续建设的 Flutter 组件库。通过增加胶水层(Adapter)代码抹平差异,当然这个胶水层是有一定规范的,那就还是可以自动生成,相当于可以无成本接入。

统一调用入口 提供单组件适配策略,在多Adapter落地的过渡阶段,可以多适配器同时使用,甚至根据不同场景相互替代。

组件分层

「面向未来」移动端 Web 组件库建设思考

规范组件 指的是 Web / Flutter 独有的生态特性组件,他们的 API、样式都是根据各自生态所约束的。

基础组件 抹平了生态特性而形成的基础组件,这些组件在使用上及样式上都是统一的。原则上不会被业务直接使用。

业务组件 是由基础组件组成的,提供给通用/特定业务使用的组件。可以被业务直接使用。

Foundation

构建规范定义层

这个规范定义层跟 FGUIComponentAPI 设计上是相同的,保证双端生成脚本可以统一,无需改动 Flutter 组件库中的代码。

「面向未来」移动端 Web 组件库建设思考

gui_component_api

「面向未来」移动端 Web 组件库建设思考

生成脚本上新增了一套 gui_component_api,而不是复用之前的 fgui_component_api。

fgui_component_api 在当时架构想法上并不成熟,产物是侵入到 Flutter 项目中,这点在更大范围的跨端上就捉襟见肘了,而且生成代码是用 Ruby 编写的,这也不符合我们现在的统一 Python 脚本的方向。

后续上,会把 fgui_component_api 移植进 gui_component_api,保持架构统一,一次定义,生成多端组件 foundation(web 组件库、flutter 组件库、iOS 调用库、Android 调用库)。

生成产物

产物以独立库的形式存在,通过跨端工具链的形式引入,后续稳定后提供 npm 包,提供规范的 constants 和 foundation。

foundation 产物举例如下:

/* eslint-disable no-unused-vars */
 
/** ------ 输入定义 ------ */
 
export const DialogLoadingProps = {
  /**
   * 是否显示取消
   */
  showCancel: {
    type: Boolean,
    default: false
  },
  /**
   * 取消延迟显示时间
   */
  cancelDelay: {
    type: Number,
    default: 0
  },
  /**
   * 内容
   */
  text: {
    type: String,
    default: ''
  },
  /**
   * 是否显示进度
   */
  showProgress: {
    type: Boolean,
    default: false
  },
  /**
   * 当前进度
   */
  progress: {
    type: Number,
    default: 0
  },
}
 
/** ------ 回调定义 ------ */
 
/**
 * 点击取消
 */
export const OnClickCancelCallback = 'onClickCancel'
/**
 * 回调: 当前进度
 */
export const UpdateProgressCallback = 'update:progress'
 
export const DialogLoadingEmits = {
  /**
   * 点击取消
   */
  [OnClickCancelCallback]: () => true,
  /**
   * 回调: 当前进度
   */
  [UpdateProgressCallback]: (progress: number) => true,
}
 
/** ------ 方法定义 ------ */
 
/**
 * 更新加载进度
 * @param progress <number> 加载进度
 */
export type UpdateProgress = (progress: number) => void
/**
 * 关闭弹窗
 */
export type Dismiss = () => void

Adapter

Web UI Adapter (Vant2)

从本意上讲,想约束住组件的出入,类似反向依赖的方式,foundation 暴露,adapter 隐藏。

但由于 Vue 的语法糖特殊性,这点实难做到开发平衡(不用语法糖,开发使用就不够友好。语法糖又限制了抽象构建的能力)。

所以这里采用的是根据组件定义,生成开发模版的思路。通过增加脚手架命令行,生成组件到指定位置即可。

生成开发代码示例如下:

<template>
  <div :class="bem()"></div>
</template>
 
<script setup lang="ts">
import { DialogLoadingFoundation, DialogLoadingConstants } from 'gui-component-api'
 
const { bem } = DialogLoadingConstants
 
const props = defineProps(DialogLoadingFoundation.DialogLoadingProps)
 
const emit = defineEmits(DialogLoadingFoundation.DialogLoadingEmits)
 
const updateProgress: DialogLoadingFoundation.UpdateProgress = (progress: number) => {}
 
const dismiss: DialogLoadingFoundation.Dismiss = () => {}
 
defineExpose({
  updateProgress,
  dismiss,
})
</script>
 
<style lang="less" scoped>
@b: ~'gui-dialog-loading';
 
.@{b} {
}
</style>

Flutter UI Adapter(Flutter Components)

这个适配器原则上会从定义层统一生成,无需开发手动介入。

Index

入口层上采用人工维护,当前指定的组件是哪个 Adapter 下的组件 export 即可。

后续

虽然笔者长期看好 Flutter for Web 的未来,但现下确实不能直接使用 Flutter 来做 Web 组件。

后续开发上,Vant 也只是一个手段,可能也会多个手段结合来快速产出产物。在 Flutter 成熟或者有更好的方案后,无痕替换掉,也不会影响上层调用。


感谢阅读,如果对你有用请点个赞 ❤️

「面向未来」移动端 Web 组件库建设思考