维护成本高
首先,在技术栈发生变更时,需要花大量精力迁移新技术栈的组件库。其次,在快节奏的互联网环境下,业务调整非常快,业务组件的需求变更非常频繁,意味着后续需要不断地在新旧技术栈下开发和维护多个“外壳不同,逻辑相同”的组件,维护成本非常高。
视觉交互不一致
新旧技术栈下的技术策略不同,导致采用的基础UI库可能也不同。以网易严选为例,2019年以前,技术栈是Angular,所维护的基础UI库是自研的Shark,而技术栈转换成React后,采用Antd作为基础UI库。由于Shark和Antd在设计上不完全一致,导致新旧系统整体的视觉和交互会有轻微的差异。
升级困难
由于环境强耦合性,后续如果要进行React升级或者使用Vue等其他技术栈,必然又需要经历同样的阵痛,如此往复,耗费大量人力,组件库的稳定性较差,后续升级难度重重。
基于以上痛点,我们希望能探索一套环境无关、便于接入、完全自治的跨框架组件开发模式。
我们前面说到,不同框架、同一框架不同版本无法共存,导致组件无法跨框架复用,甚至只能固定在框架的某个版本,这与前端未来的模块化发展是相违背的。庆幸的是,W3C于2011年提出Web Components的概念,并与2014年产出v1草案,使浏览器原生支持模块化。Google也一直致力于Web Components的发展,率先在Chrome完成了浏览器的底层支持。
Web Components的三驾马车,即Custom elements、Shadow DOM和HTML templates。
1)Custom elements
允许用户自定义一组JS API,完成UI和逻辑行为,这与主流框架中的组件自定义元素的概念是一致的。
2)Shadow DOM
自定义元素将生成一棵与主文档隔离的“影子”DOM树,而Shadow DOM的作用就是将这棵树附加到宿主元素中,保证了元素功能的私有和独立性。
3)HTML templates
提供了 template 和 slot 功能,用于自定义元素结构和模板复用。
2017年以前,Web Components的发展是较为缓慢的,毕竟标准的推进需要各大浏览器厂商的支持,过程漫长,各大公司都在观望。到了2017年,陆续有公司开始使用Web Components构建UI库。但是原生的开发方式非常繁琐,有不少团队开始研究Web Components的开发框架和编译工具Ï,通过更灵活、便捷的方式开发编译出Web Components。其中,Svelte团队的Svelte框架,Polymer团队的Lit-html、腾讯的Omi,都取得了不错的效果。
1)优点
框架0耦合
浏览器原生支持
主流框架支持
未来组件化趋势
2)缺点
兼容性
目前原生浏览器中只有Chrome和Firfox提供了全面的支持,IE和Edge等并不友好。庆幸的是,官方polyfill可以提供IE11以及Edge和其他大部分浏览器的支持,这对B端产品来说,已经够了。
数据绑定
状态管理
开发成本
Web Components目前并没有数据绑定和状态管理,导致我们无法像其他主流框架那样方便地更新视图、使用redux等进行数据状态管理。
在CFC架构设计中,网易严选借鉴了许多外部的优秀方案与设计,经过长时间的探索、开发和实践,形成了有严选特色的跨框架组件开发架构体系。
基础库
底层基础库包括了技术栈、基础UI库以及其他工具库,是上层建设的基石。
组件层
组件层,采用React进行组件核心逻辑开发,这里需要可以复用底层基础UI库和其他业务组件库。
桥梁层
通过第三方工具Direflow完成React组件到Web Components的转换,从而实现内部完全自治,统一对外输出形态,上层可以接入任意支持Web Components的框架中。为了资源复用,我们将源码以UMD的方式打包并上传至CDN,通过懒加载的方式与封装层进行联通。
封装层
至此,应用层已经可以直接使用组件了,但是由于在React/Angular/Vue中使用Web Components时需要处理事件、参数变更等,使用方式较为繁琐。为了减轻组件使用者的负担,我们在封装层进行了统一的源码加载、参数传递、事件处理等中间转换。最终,对外发布的npm包和现有多套组件的使用方式是一致的。
基础包
收纳与框架无关,又需要在Web Components和不同框架之间复用或者对外暴露的文件。
包服务管理
用于管理包依赖和加载顺序。
Direflow是一个将React框架转成Web Components的第三方工具,目前github的star数在50左右。其背后的技术很简单,利用了Web Components的生命钩子完成组件的挂载、更新和卸载。
封装层起到了胶水作用,主要完成以下功能:
1)加载Web Components及其依赖的源码
2)参数传递
参数初始化
参数变更检测
3)通信
回调函数,在封装层转化成框架的事件传递方式
全局通信,使用Event完成全局事件通知
4)base包输出
由于封装层的代码比较固定,我们提供了脚本化配置,通过几行简单的配置,实现一键生成封装层。最后,尽管需要保持封装层和技术栈的统一,在该方案下的升级和重构成本是非常低的。
通过大量落地实践,我们对该方案在React和Angular框架封装后的能力进行分析,最后得出如下结果。可以看到,在参数变更、事件、路由等常规组件能力上,React和Angular都是无损的,而在slot模板能力上,无法在React组件中自定义Angular组件。
我们在前面提到,Direflow只是一个转换工具,对外输出Web Components,其底层本质上还是React。因此,在运行时会加载React库和UI库。在落地的时候发现,单个业务包的大小达到1.2M,无法忍受。所以我们加入了资源复用,将React相关套件、UI库以及Direflow剥离出去,通过包加载管理和浏览器缓存以达到复用目的。最后核心业务包部分控制在几十K。
然而,要做的事情远不止这些,我们通过管理命名空间、收敛基础库版本、管理包依赖以及CDN服务的方式缓解了包大小和包冲突的问题。
包冲突
管理包命名空间,以隔离影响
版本收敛
试想一下,如果我们的一个应用场景里用到的多个组件包底层的React都是来自于不同的版本,虽然不冲突了,但是复用率将大打折扣。另外,多个框架在一个环境会占用大量内存,影响页面性能。所以我们通过管理包服务以及代码检测的方式进行版本收敛,在底层React版本始终加载大版本下的最新小版本,相同环境内的React版本不应该超过2个。
包依赖
通过包管理服务管理业务包依赖,在懒加载的时候进行按序加载,保证执行顺序,避免重复加载和冲突。
加快加载速度
将所有资源上传到cdn,以加快加载速度。
Shadow DOM天然的隔离性,让css的处理变得稍微复杂。
Shadow DOM内
通过style-it插入根节点前。
Shadow DOM外
在Shadow DOM外进行DOM操作,eg,全局的toast、modal,已经脱离了Shadow DOM,就需要在全局额外动态加载对应的css,并且要保证对应命名空间的唯一性。
第三方UI样式
对于第三方UI样式,比如我们现在用的antd,同样需要进行资源复用,动态加载。
效率提升
互联网寒冬时代,各大公司都在强调降本增效,提高生产力才是硬道理。从网易严选目前落地的组件包的代码量上看,人力投入可缩小至60%。
环境隔离
由于将框架和版本解耦,真正做到组件自治,未来升级技术栈的时候,也只需要新增对应的封装层即可,做到平滑升级。
交互视觉统一
因为只维护了一份代码,组件在不同技术栈下的表现可以保证一致。
综上,本文通过介绍网易严选跨框架组件开发模式的方案设计和落地细节,力求保证组件库的隔离性、一致性和稳定性,希望给大家提供一个新的思路。
作者简介
露艳,网易高级WEB前端开发工程师。2017年北京邮电大学硕士毕业,同年加入网易,先后参与多个数据项目,目前致力于网易严选前端基础开发资源建设。
本文由作者授权严选技术团队发布