微前端实际应用案例剖析(wujie)

4,371 阅读14分钟

导读:微前端是一种将单个应用程序分解为多个小型、可独立开发、可独立部署的应用程序架构,常见的运用场景是多个新旧子应用聚合为一个完整的产品进行交付,本文主要介绍梯度前端研发部如何基于前沿开源的前端技术方案,实现微前端在大数据平台中的应用落地,并取得了良好效果的案例剖析。

案例背景

根据公司产品某项需求,需要引入一个新应用作为一个独立模块集成进已有大型平台。公司本身产品的前端技术栈与待引入的新应用技术栈不同,分别是采用的是vue和react,使用的UI框架也截然不同。新应用可以独立开发部署,但为了产品体验的一致性,需要将新应用嵌入已有业务中,并通过少量的改造使样式和功能融为一体,以期用户在使用时感受不到区别。

  • 1.0阶段:采用外链跳转的方式,点击菜单打开新tab页展示数据开发模块功能
  • 2.0阶段:采用iframe内嵌页面的方式,点击菜单在当前页面通过iframe加载url
  • 3.0阶段:采用wujie微前端方案进行父子应用改造,组件式加载子应用方便快捷
  • 4.0阶段:利用微前端能力实现模块拆分节约50%工作量,新增跨模块功能不再受限于技术栈

在引入wujie微前端方案后,对大数据平台产生了积极和深远影响:

收益变现

  • 主框架不限制接入应用的技术栈,微应用具备完全自主权,做到新技术选型时“技术栈无关
  • 微应用仓库独立,部署完成后主框架刷新时自动完成同步更新,每个微应用之间状态隔离,运行时状态不共享,独立运行时
  • 在面对各种复杂场景时,对已经存在的系统做全量的技术栈升级或重构不现实,而微前端是一种非常好的实施渐进式重构的手段和策略,能够做到增量升级
  • 便捷的alive保活模式,可以确保在用户经常切换路由的场景下不丢失编辑状态,非常适合大数据平台的离线开发、实时开发、算法开发、低代码等业务场景

持续增值

  • 开启preloadApp预加载后,能显著减少白屏时间,结合保活模式可获得极速的打开体验
  • 支持 vite 等 ESM 脚本运行,想要部分模块升级最新的vue3?不用怕,干就行了
  • 提供无感知的iframe降级方案,理论上兼容到IE9,配合插件系统能解决99%的兼容问题
  • 未来接入其他第三方应用(如BI、3D)将成为刚需,可无痛苦、无负担的扩展子应用

微前端解决方案对比和决策

可能有人会问目前市面上主流的微前端方案有很多,你们为什么选择wujie微前端方案呢?在对比和决策的时候你们是怎么做的?那在这之前我们还是先来了解一下什么是微前端:

什么是微前端?

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略,英文名称叫 micro-frontends

  • “微前端”这个名词,第一次被提出还是在2016年底,微服务这个被广泛应用于服务端的技术范式开始扩展到前端领域。
  • 趋势:前端越丰富复杂,项目越庞大难以维护
  • 核心思想:独立的团队负责特定的业务和开发独立的功能模块

架构对比

接下来我们来看一下现在前端最常见的几种架构:

image.png

需要注意的是,这些技术并不是相互排斥的,实际应用中可能会使用它们的组合来实现应用程序的需求。例如,同构应用可以与JAMStack和微前端一起使用,以提高应用程序的性能、可维护性和可扩展性。

实践经验告诉我们,永远不要追求最好的架构,而要追求最不糟糕的架构。真实世界里完美的架构并不存在,架构也没有对错之分,只有根据环境进行利弊权衡后的最佳结果。

微前端决策

在选择何种方案决策的时候 我们整理了一份决策要素清单:

image.png

参考这份清单给出的建议我们可以全方位的对比每个框架的优缺点,帮助我们选择最适合当前应用的方案。我们选取了目前市面上最流行的几种方案进行技术调研:

  • 阿里开源的 qiankun 方案,基于 single-spa
  • 京东开源的 micro-app 方案,基于 webcomponent + qiankun sandbox
  • 欢聚时代开源的 EMP 方案,基于 webpack5 module federation
  • 字节跳动开源 Garfish 方案,这是一个集部署、框架、调试于一体的全套解决方案
  • 腾讯开源的 Wujie 方案,基于 WebComponent 容器 + iframe 沙箱

详细调研了它们的优缺点之后(篇幅限制这里省略,具体可参考各方案官网),我们根据主客观判断给出了一个评分表:

image.png

为什么不是iframe?

最终我们经过多方面的对比和分析,并且在基于产品研发进度很赶、使用和改造成本要尽可能低的实际情况下,决定选择各方面都比较适合的wujie方案

实际应用剖析

这部分内容主要介绍wujie的实施方案和细节,对应的利用了哪些wujie提供给我们的能力,以及解决了具体开发过程中哪些问题和最佳实践。

控制台展示

话不多说,先直接从浏览器按F12调出开发者工具,查看大数据平台实施微前端改造后的父子应用前端代码结构:

image.png

可以从元素窗口看到,引入wujie后html里面的实际结构,其中wujie-app就是wujie创建的webcomponent自定义组件,可以理解为当前位置之前使用的iframe标签,现在把标签替换为了wujie-app,里面的内容就是子应用也就是引入的新模块的内容,外面的内容就是父应用也就是原来已有的部分。

另外可以看到该自定义组件里面只有dom元素和css样式内容,那最关键的js代码去哪里了呢?继续往下翻阅代码到底部,发现在最底下多了一个iframe标签,这个标签的路径是当前网页的路径,样式是隐藏的状态。

image.png

可能有做过前端开发的小伙伴立马发现了端倪,原来子应用的js是通过一个iframe沙箱的形式加载进来的。后面会详细介绍js沙箱和css沙箱是如何建立联系的,这里先暂时做个大概了解,至少我们现在知道了在wujie微前端框架的影响下,子应用的js和dom元素以及css样式是分开的即可。

wujie使用

前面我们说到wujie的组件式加载方式非常方便快捷,那我们还是先简单了解一下wujie的vue组件的使用方式。

图片1.png

wujie基于vue2、vue3、React框架的组件封装了对应的npm包:wujie-vue2、wujie-vue3、wujie-react

  • 父应用安装并全局引入wujie-vue2
  • 常规的使用组件方式和props传递参数,与普通组件别无二致
  • 子应用本身无需安装任何包,无界对子应用注入了$wujie对象,可以通过多种方式获取

也就是说在满足跨域条件下子应用可以不用做任何改造。直接把iframe替换为wujie组件,就这么简单!

需要特别注意一点的是wujie的运行模式,子应用是否开启了保活以及是否进行生命周期的改造会进入完全不同的处理流程

图片3.png

保活模式、重建模式下,子应用无需做任何改造工作,而单例模式则需要做生命周期改造。

此外框架提供的十余种具体的能力(例如灵活的路由跳转和同步、多种方式的通信系统、强大的插件系统、无限应用嵌套、避免浪费的应用共享等等)在这里不再赘述,请参考wujie(无界)官网

实际应用方案

以下是我们根据项目实际情况使用的实施方案和原因理由,需要说明的是在不同公司不同项目是完全可以根据需求和wujie提供的各种能力来灵活调整方案的,这里并没有什么最佳实践,个人认为只要能很好的满足需求就都是最佳实践。

保活模式

选用保活模式的原因主要有以下几个方面:

  • 子应用模块代码加载量大,保活模式只需一次加载,避免频繁切换白屏。
  • 用户在子应用中使用monaco编辑器编辑内容时容易忘记保存,用户不希望退出当前页面就丢掉了未保存的输入内容。
  • 子应用模块里面的底座是用了另外一个高度封装的Web IDE UI框架,生命周期改造需要修改底座源码,改造起来需要投入大量的时间成本。

props通信和EventBus方式结合

  • 我们需要用到子应用模块里面不止一个页面,且某几个页面要独立出来,方便后续做权限控制。
  • 通过location.query参数不同判断展示
  • 通过props传递模块类型和jump方法
  • 通过bus.$onbus.$emit监听和触发子应用的路由变化

两个wujieVue组件

  • 3.0阶段后需要将子应用模块拆分成两个模块,分别有两个入口,所以用两个组件来区分。
  • 父应用有两个wujieVue组件,每个组件对应多个页面,props传不同参数。
  • 子应用只需要一份代码,巧妙的通过参数区分接口层、业务逻辑层,并在内部发生路由跳转时通知父应用处理,节约了50%以上的开发时间和人力成本。

服务代理

image.png

这是父子应用独立部署两个服务的情况图,父子应用之间用nginx做路由跳转和接口代理

页面结构

image.png

这是页面结构示意图,可以看到绿色部分即原有平台页面,菜单导航都还是在原有代码里面的,而新增的蓝色部分就是用wujie包裹的部分

数据流程

数据流程.png

这是我们的路由和参数传递流程,可以看到我们是用props和EventBus自己搞了一套路由同步逻辑的。

为什么我们没有采用官方提供的 路由同步 功能,而是相当于自己手动实现了一套路由同步逻辑呢?这里最关键的原因在于首先我们的子应用(第三方模块)不能够或者说非常不方便做生命周期改造。

官方的文档说:只有无界实例在初次实例化的时候才会从url上读回路由信息,一旦实例化完成后续只会单向的将子应用路由同步到主应用url

这里有个坑就在于开启路由同步后,刷新浏览器或者将url分享出去子应用的路由状态都不会丢失,会导致子应用永远也不会刷新。如果子应用是单个页面还好,但我们是多个页面要通过query参数来区分不同页面,必须要通过更新路由状态来切换不同的页面。

另外回顾一下无界的运行模式,做了生命周期改造才能使用单例模式,而使用了单例模式才能够实现改变url子应用的路由才会跳转到对应路由。我们不能或不想做生命周期改造,也就没有用路由同步模式。

这里情况有点复杂,但如果你在实际实施过程中,多翻翻官方文档,或者深入到源码里去找找问题答案,或许就会更加明白什么是可行的什么是不可行的,对使用任何一种技术方案都是非常有帮助的。

无界框架源码与原理

接下来我们更深入的研究一下wujie的源码和原理,对我们理解如何更好的使用微前端大有裨益。

wujie-vue2组件如何封装

image.png

可以看到官方提供的wujie-vue2组件十分简洁,核心就是调用了wujie的startApp方法而已

wujie-core代码结构

image.png

可以看到wujie的核心代码也十分简单,总共14个文件,入口在index.ts,可以顺着入口一点一点深入源码进行了解。

wujie-core核心代码思维导图

wujie-core核心代码思维导图.png 链接

这里我们也整理了一份思维导图,感兴趣的读者可以从github上clone出wujie的源码,对照着思维导图阅读源码,对深入理解wujie核心工作原理有非常大的帮助

js 沙箱和 css 沙箱连接 原理和细节

  • 子应用的实例instance在iframe内运行,dom在主应用容器下的webcomponent
  • 无界在底层采用 proxy + Object.defineproperty 的方式将 js-iframe 中对 dom 操作劫持代理到 webcomponent shadowRoot 容器中,可以实现两者的互联,开发者无感知也无需关心。
  • 细节:将document的查询类接口:getElementsByTagName、getElementsByClassName、getElementsByName、getElementById、querySelector、querySelectorAll、head、body全部代理到webcomponent,这样instance和webcomponent就精准的链接起来。
  • 当子应用发生切换,iframe保留下来,子应用的容器可能销毁,但webcomponent依然可以选择保留,这样等应用切换回来将webcomponent再挂载回容器上,这样子应用就可以获得类似vue的keep-alive的能力。

webcomponent和proxy的降级方案

  • 在非降级场景下,子应用的dom在webcomponent中,运行环境在iframe中,iframe对dom的操作通过proxy来代理到webcomponent上
  • 而webcomponent和proxy IE都无法支持,无界采用另一个的iframe替换webcomponent,用Object.defineProperty替换proxy来做代理的方案
  • 降级的行为由框架判断,当浏览器不支持时自动降级
  • 降级后,应用之间也保证了绝对的隔离度
  • 代码无需做任何改动,之前的预加载、保活还有通信的代码都生效,用户不需要为了降级做额外的代码改动导致降级前后运行的代码不一致

实践总结

以上我们介绍了微前端在大数据产品中的应用背景、应用理由、应用方式和应用原理,当然我们在实践过程中也不是一帆风顺的,中间也踩过不少坑走过不少弯路,也遇到过一些非常棘手一时无法解决的问题,所幸还有很多社区的小伙伴们给予了很大的支持和帮助,梯度科技前端研发部也将更多的反馈回报给开源社区助力一同成长。

常见问题

跨域问题和cors设置

可能的原因分析:

  • 子应用的资源和fetch接口的请求都在主域名发起,所以会有跨域问题,子应用必须做 cors 设置。
  • 资源或接口请求没有携带 cookie
    • 子应用本身是用fetch发起请求,需要将子应用fetchcredentials设置为include,这样cookie才会携带上去。
    • 或者在主应用自定义fetch并将fetch的credentials设置为include。

子应用弹框位置不正确

  • 冒泡系列组件(比如下拉框)弹出位置不正确
    • 解决方案:子应用将body设置为 position: relative 即可
  • 子应用弹窗根据点击事件的 event.clientY 来确定top位置,但是主应用头部有导航栏导致位置计算不准确
    • 解决方案:子应用弹窗dom元素添加 position: fixed 样式即可

社区优秀插件

wujie-polyfill

由于wujie(无界)采用的是WebComponents + iframe 来是脚本沙箱和样式隔离,该仓库用于弥补该方案的在特定的场景下的不足。

插件列表:

  •  LocationReloadPlugin (页面刷新插件)
  •  EventTargetPlugin (事件目标插件)
  •  WindowGetterPlugin (window获取插件)
  •  WindowMessagePlugin (window通信插件)
  •  DocFullScrollPlugin (全屏插件)
  •  InstanceofPlugin (原型链判定插件)

基本上目前常见的子应用各种问题和坑都能在社区找到解决方案

使用心得

  • 不敷衍了事,善于总结复盘,避免重复出错。

    • 调研:全面细致,仔细对比各项优缺点,调研结果总结成文,后续可重复利用。
    • 选型:根据实际情况,结合业务需求,充分权衡利弊,并适当考虑可扩展性。
    • 踩坑:踩坑过程中肯定会遇到问题和困难,多问、多看、多分析,借助社区力量。
  • 树立精品意识,用心把每件事做到极致。

    • 勉强能用就行了吗?
    • 将来还有没有更好的解决方案?
    • 有没有漏掉某些问题,只是暂时没有被发现?
  • 不甘现状,保持好奇,持续学习,学以致用。

    • 前端的新轮子不再高频率出现,核心框架开始挤牙膏,甚至是互相借鉴。
    • 趋势已从追求轮子上的广度,转变为聚焦核心场景,围绕深度上进行探索。
    • 在稳定性和开发者体验上做更多努力。
  • 遵循但不拘泥于工作流程,化繁为简,用较小的投入获得较大的工作成果。

    • 刚开始使用Wujie时还是RC版本状态(目前V1.0.6),1-2个月后交流群里人数明显增多
    • 重复利用已有的代码,多个应用共用一份,改造成本很低,节省开发工作时间60%以上
    • 提升前端团队整合第三方的能力,积极应对将来可能出现的复杂局面

未来方向思考

以上是我们进行微前端使用过程中的全部介绍,可能有很多不足的地方,期待大家的反馈和交流~ 那之后微前端还会往哪个方向发展呢?欢迎大家在评论区讨论哦!

后续计划还会推出《Vue和React快捷集成工具包实践总结》介绍在vue中使用react组件和在react中使用vue组件的骚操作,以及对已有react/vue组件进行二次封装发布npm包的基础教程等。期待关注!