关于跨平台跨框架同构组件库的一些尝试
组件库是个老生常谈的问题,市面上的同构组件库基本上都属于框架的生态,要求跨端的应用是同个框架。
而医典所遇到的问题是,我们的框架并不统一,如何针对这种情况,构建一个同构组件库,在组件层面抹平框架、平台的差异。然而市面上并没有一个成熟的解决方案。这就需要我们在组件库打包、框架上面做一些定制化的修改。如果你也遇到过同样的情况,可以参考我们的实现方案。
1. 背景
1.1 产品形态和技术栈
从产品角度来说,腾讯医典主要是由三个应用组成:医典H5、腾讯医典小程序、腾讯健康小程序。
从技术选型来说,由于历史原因,腾讯健康小程序选择了mpvue框架,腾讯医典小程序选择了uniapp框架,医典H5选择了Nuxt框架。
由于腾讯健康小程序是几个团队共同维护的,我们选择了子仓的方式来进行代码融合。虽然后面引入了小程序微前端的方案来抹平框架差异,但在本地调试时依然是选择mpvue的方式进行运行,所以组件库的兼容依然需要考虑到mpvue框架。
1.2 产品开发中的痛点问题
在产品开发中,我们发现两个迫切需要解决的问题:
1)组件复用性差 + 代码逻辑耦合,导致组件体积过大,影响页面加载速度和用户体验
2)小程序和H5的业务组件各自维护在业务项目里:开发维护成本高、走查成本高,有时甚至会造成双端用户体验不一致




那能不能在组件层面抹平平台和框架的差异呢?
1.3 解决方案对比
可以选择的组件库开发组件无外乎两种:
第一种,小程序自定义组件。但这种方案有两个问题:
1)医典H5是Nuxt框架,无法运行小程序自定义组件;
2)医典小程序使用的是uniapp框架,针对小程序自定义组件会直接拷贝,不会使用uniapp引擎来做底层自动diff
第二种,使用 vue 组件。
这种方案契合医典目前的技术栈,医典小程序可以充分利用uniapp引擎来做底层自动diff,而且通用Vue UI组件库也更为成熟。
另外,Uniapp生态其实是提供了同构组件库uni-ui的,但是它有两个问题:
1)uni-ui 组件库和 uniapp 框架强绑定。无法适用于医典 H5 的 Nuxt 框架
2)uni-ui 组件库只提供 Vue 组件。医典小程序存在大量小程序自定义组件,无法直接引用Vue组件。
2. 如何同构
2.1 整体构建流程
考虑到音视频组件等组件依赖的sdk完全不同,组件库的整体构建流程,我们采取了同构为主、异构为辅的方案。组件库的开发组件主要是vue组件,部分组件会是小程序自定义组件。
针对H5平台,vue组件会打包成三种格式:esm、cjs和umd;
而针对小程序平台,我们会同时提供vue组件和小程序自定义组件。vue组件会条件编译为小程序平台的vue组件和小程序自定义组件,而编译后的小程序自定义组件可以直接被src下的小程序自定义组件使用。




下面再看下针对同构组件的构建流程。我们会用到三个工具来进行构建。H5平台我们使用rollup,小程序平台由于需要两种组件:vue和小程序自定义组件,所以我们使用gulp来做条件编译,再基于编译结果,使用自研的编译器来编译成小程序自定义组件。




2.2 模板同构
模板同构主要会遇到三种情况。
2.2.1 Block Template 差异
比如,在医典场景下,操作栏大多为横版,但因为sigusoft浏览器下底部会自带操作位,如果叠加两个横版操作栏,用户体验并不是很好,所以才有了竖版操作栏。也就是说竖版操作栏仅H5需要,而小程序是完全不需要的。




这里当然可以用v-if来做根据运行环境在运行时进行判断是否展示,但是会额外增加打包的模板体积。能不能在编译阶段就把竖版template的逻辑剔除掉呢?
我们用注释标出特定平台需要的模板,再通过条件编译来处理。具体细节见下图。




2.2.2 Event API差异
小程序的button组件会内置一些开放能力,比如getPhoneNumber等来用户信息。




DOM事件里完全没有getPhoneNumber、getUserInfo这些事件,所以这些开放能力相关的回调在H5里是不会被触发的。但很多时候,我们的业务需求是两个平台都需要支持点击按钮触发不同开放能力回调的,比如填写评论时,无论小程序和H5,我们都需要用户头像。
为了增强可维护性,无论是template绑定的属性和事件还是对外暴露的event,那怎么做呢?
先来看下绑定的属性open-type。这个属性并不属于dom element的属性,所以在打包过程中我们可以在vue-template-compiler的processAttrs方法里将其过滤掉,而针对小程序的平台,直接保留相关属性。




再看下绑定的事件回调,我们可以通过条件编译组件方法来进行处理。在开发组件里,H5平台下click 事件回调里,会根据openType 类型调用不同方法;而小程序平台,click事件回调只处理click事件相关。




2.2.3 vue模板如何编译成wxml格式
因为医典存在大量小程序自定义组件,而它不能引用vue组件,所以我们需要做vue到wxml的转换。
uniapp自带的compiler,只能将vue组件编译成供uniapp引擎消费的wxml。
下面图中可以看到click事件绑定的是__e的回调,这样编译出来的wxml是不能直接被小程序自定义组件引用的。所以我们需要在uniapp模板编译器做进一步的改造。




那如何基于 uni-template-compiler 做定制化, 让编译后的wxml能直接被小程序自定义组件使用呢?
主要会有三个方面的改造:事件绑定、ref、slot。
这里仅以事件绑定加以说明。首先,我们会将模板转为AST,遍历节点时我们会去处理事件属性,进一步到回调函数名、回调参数和自定义参数值,通过这三项,我们可以将内部代理事件替换为onClick、组装自定义参数data-open-type。




2.3 js 同构
2.3.1 第三方依赖条件编译
C语言使用#ifdef #ifnondef等指令来做条件编译,我们也借鉴了同样的方式来处理JS同构。
具体来说,我们会在rollup插件和gulp插件里注入平台变量,通过匹配指令的方式来生成对应平台的JS代码。




2.3.2 自研跨端API SDK
除了条件编译,还会遇到DOM API 无法在小程序平台运行的情况。
uniapp本身是提供跨端SDK,但它有两个问题:第一,和uniapp框架耦合,无法适用Nuxt和mpvue框架;第二,它是全量挂载到uni对象上,无法做按需加载。考虑到这两点,我们需要自研适合医典的跨端API。




下面以IntersectionObserver为例, 来说下API的设计思路。这个API是用来监听元素是否在可视区域里的,双端api的使用方式、出入参都差异很大。针对这种情况,我们使用了IObserver来做代理,分发事件处理。为了统一使用方式,我们将实际observer实例的创建延迟到调用observe方法时。另外,我们新增了reconnect方法,避免API误用导致的浏览器频繁GC。




2.3.3 vue组件script部分如何编译为小程序Component构造器
和模板同构类似,为了让小程序自定义组件能使用,我们也需要将vue组件的script部分编译成小程序的组件构造器。由于我们项目里同时存在ts、js的script,兼容两种情况的 compiler 实现成本太高,我们采取了折中的方法。先将script用rollup编译成esm格式的js,再做相应的编译处理。




那具体编译转换这块是怎么做的呢?我主要说两点:
第一个是计算属性的转换,我们会以计算属性的key创建一个新的node,并插入到data里,并创建一个方法来更新这个data属性,在attached生命周期时初始化这个data,与此同时在小程序数据监听器里去调用这个方法。
第二点是,小程序的组件构造器会有options选项,比如如果模板里有多个slots,就必须设置multipleSlots选项,才能生效。针对这种情况,我们在转换模板步骤时会去判断是否有多个slots,并缓存组件选项,待到script解析时再生成相应选项。
3.总结
3.1 同构组件库整体概览
目前医典同构组件库已上线近一年,服务百万医典用户。




3.2 同构组件库对业务的价值
由于大部分组件只用维护一套代码,极大地提高了我们需求交付的速度,并大大降低了现网bug数。平均需求交付数量提升2.22倍,现网 bug 数减少77.76%。
由于我们组件库的组件统一使用了声明式埋点,数据埋点问题排查的速度得以大大提升。一般来说,数据埋点问题是需要开发人员协助在代码逻辑里排查的,而使用了统一的声明式埋点后,数据同学可自行排查。
另外,由于组件是同构的,双端的错误监控码得到统一,bug修复也只用在一个地方进行修改,大大提高了线上问题排查和现网bug的修复效率。
如果你的业务场景也遇到了跨端框架构建组件库的问题,欢迎和我交流。
End.
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/9220.html