草庐IT

基于 iframe 的微前端框架 —— 擎天

vivo 互联网技术 2023-03-28 原文

vivo 互联网前端团队- Jiang Zuohan

一、背景

VAPD是一款专为团队协作办公场景设计的项目管理工具,实践敏捷开发与持续交付,以「项目」为核心,融合需求、任务、缺陷等应用,使用敏捷迭代、小步快跑的方式进行开发及质量跟踪,简化工作流程,帮助团队快速迭代并高效完成产品开发交付。

但早期VAPD以“一切皆可配置”的设计理念开发运行了两年,整个前端代码复杂混乱,组件庞大(需要支持多种配置),状态混乱,前端代码打包出来有50M之巨。这个项目难以为继,bug多、维护困难、新增功能处处受限,总之产品不满意、测试不满意、用户不满意。

 

因此改版是必然的选择,而改版的要求就是不能耽误用户继续使用,必须保证网站可用、逐步更新,因此微前端是必然的选择。

VAPD改版思路就是:

  1. 使用微前端框架,未改版部分作为子应用存在,继续为用户服务;

  2. 将项目模块制定系统应用,并逐个改版,降低项目复杂度;

  3. 逐步舍弃旧项目代码,将功能转移到新项目中,提升项目整体性能,提高代码可维护性。

二、什么是微前端

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. —— Micro Frontends.

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

微前端将微服务的理念应用于浏览器端,即将单页面前端应用由单一的单体应用转变为把多个小型前端应用聚合为一的应用,各个前端应用独立开发、独立部署。

微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

现行最出名的微前端是阿里的qiankun,qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

但我尝试试用qiankun后发现qiankun 的npm包常常滞后于qiankun源码,有些issue解决了但还要等待其发版;首次加载子应用页面出现抖动;子应用更新后报 ChunkLoadError 问题 。 

同时它存在微前端框架常有的性能问题及冲突问题:

1、加载慢

基本上所有的微前端框架都需要先加载父框架,再加载子应用,都要经历如下图的流程。整个流程是串行的,相同流程需要走两遍,也就比普通的非微前端框架要慢1倍左右,直接影响了用户体验。

 

 2、切换慢,每次切换都要重新走一次流程

微前端框架中不同子应用切换,需要销毁当前子应用,然后加载其他子应用。子应用又需要进行“下载html --> 下载javascript文件 --> 运行javascript代码 --> 请求服务端数据 --> 页面展示"整个流程,导致微前端框架切换应用卡顿不流畅。

 3、容易出现样式冲突,不同子应用容易影响

传统微前端框架中所有子应用都在同一上下文中,如出现全局样式、全局监听、全局变量,便会对其他子应用有影响,容易出现样式冲突,内存泄漏,或者不可预知的bug。

三、Why Not Iframe

iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、JS隔离这类问题统统都能被完美解决。

那为啥不使用iframe呢?

qiankun 团队也给出了原因:看这里 Why Not Iframe

总结起来就是:

  • url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。

  • UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..

  • 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。

  • 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

其中有的问题比较好解决(问题1),有的问题我们可以睁一只眼闭一只眼(问题4),但有的问题我们则很难解决(问题3)甚至无法解决(问题2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。

四、擎天框架设计

4.1 设计理论

基于以上微前端缺陷,设计新型的微前端框架需要满足以下要求:

  • 快 —— 页面加载速度、应用切换速度是前端极致的追求,用户操作的响应速度永远是第一体验。

  • 完全隔离 —— 不同子应用完全隔离,只在子应用项目初始化时设置一次,之后不需要在后续开发中及后期维护中考虑,降低开发的心智负担。

4.2 架构设计

擎天框架架构分为三层,分别是用户入口(浏览器地址栏),主应用层以及子应用集合层。

(1)浏览器(地址栏)是用户入口,用户通过输入URL进入该系统,同时浏览器地址栏URL根据应用页面进行变化,方便用户复制分享二次进入。

(2)主应用是擎天框架的关键部分,主要是由以下两部分组成:

  • 路由引擎:实现浏览器地址栏与子应用展示隐藏的协调控制,用来控制用户第一次进入展示某个应用,当用户切换页面时需同步浏览器地址栏(方便用户复制,再次进入同一页面),并根据页面URL展示隐藏子应用。

  • 数据共享引擎:实现子应用间的数据共享,保证各个应用间数据统一,如登录信息,用户信息等。用户在某个应用修改共享数据后,会同步到数据共享引擎,再分发给其他应用,从而保证共享数据一致。

(3)子应用集合层

该层为系统提前设置好的子应用集合,子应用由全屏iframe加载,同一时刻仅有一个子应用处于可视状态,其他子应用隐藏。当需要应用切换时,隐藏当前应用,显示需要展示的应用,瞬间切换。

子应用响应擎天的路由引擎及数据共享引擎,做到实时的路由同步与数据同步,保证整个微前端项目路由及数据统一。

五、擎天框架实现

擎天框架突破了 iframe UI不同步、URL不同步、数据不共享以及加载慢等问题,并将iframe作为页面容器存在,在实现硬隔离的同时做到了子应用瞬间切换,解决了微前端框架一直以来的通病,从而实现单应用级别的操作体验。

5.1全屏iframe、共同组件

解决问题:UI 不同步

全屏iframe是指整个浏览器窗口全部给子应用iframe,子应用接管浏览器所有功能,无论是全局拖动、全部蒙层、resizie等,由此UI不同步的问题便不存在了。

但不同应用有个相同的公用部分,因此需要把公共部分做成统一组件,发到npm包中,在每一个应用中引入就行。

 

5.2 父子应用异步加载

解决问题:加载慢

在前端静态页面中预留子应用dom节点,如下图 所示,每一个div有唯一id,代表一个预制子应用。同时该id对应子应用路由pathname,如New对应/New/*路由,即以New开头的路由全部对应id为"New"的子应用。

当用户进入页面后,父框架拿到浏览器url,并获取到pathname,从而知道需要首先加载那个子应用。并且直接创建iframe,并直接挂到对应的dom节点中,父应用和子应用异步加载。

加载完首个子应用后,开始加载其他子应用,并使用display: none将它们隐藏到页面dom结构中。最终dom节点如下图所示。

5.3 子应用iframe瞬间切换

解决问题:子应用切换卡顿

用户进行多个子应用切换时,擎天框架监听浏览器url地址,如pathname从/New/*变成/Web/*,则将/New/*对应的子应用iframe隐藏起来,将/Web/对应的子应用iframe显示出来,实现应用的瞬间切换。

用户可视范围内只能看一个应用,切换时仅仅是将New应用隐藏不可见,Web应用页面可见。

 

 

5.4 路由引擎,同步切换

解决方案:URL不同步

受vue2中数组方法(如push、shift)响应式处理的启发,擎天对前端路由框架进行特殊处理,重写了vue-router的push、replace等方法,当监听到子应用使用以上方法进行路由切换时,会同步到父框架进行操作。

父子应用简单配置后,父子路由同步便做好了。路由切换分为单应用以及多应用间路由切换。

(1)单应用路由切换

单应用子iframe路由切换,如/New/b 切换到/New/c,其pathname结构一致,是单应用的路由切换。子应用先切换路由,随后发送syncRoute给父框架,父框架使用replace方法改变浏览器地址栏即可,具体流程如下图所示。

 

 

(2)多应用间路由切换

如/New/b 切换到/Web/c,其pathname结构不一致。子应用先切换路由,随后发送syncRoute消息给父框架,父框架使用replace方法改变浏览器地址栏,同时将应用A隐藏起来,并发送消息到子应用B中。子应用B获得消息后修改其本身路由。

5.5 数据共享

解决问题:内存变量不共享

父应用将需要共享的数据放到store中,并使用syncStore进行注册。

子应用通过类vuex似的 mapGlobalState 方法可以获取父应用store中数据,同时该数据为响应式,数据变更可触发UI重新渲染。

通过mapGlobalMutations获取修改数据的方法,通过该方法可以修改父应用store数据,修改成功后擎天共享数据引擎将修改好的数据分发给各个子应用,保证共享数据一致。

 

其内部逻辑是:

父应用将需要共享的数据放到store中,并暴露getTopStore(获取store数据)、syncTopMutation(更新store数据)、syncTopStore(分发store数据)等接口。

系统加载时子应用通过getTopStore方法可以获取store中数据,并赋值到子应用$pstore中,从而获得数据响应式等能力。

当某个子应用需要修改共享数据时,调用syncTopMutation方法进行修改,修改成功后擎天通过syncTopStore分发给各个子应用,保证共享数据一致。

六、总结

擎天基于全屏Iframe与搭建公共组件等方式,解决了iframe UI不同步的难题,并充分发挥了iframe作为页面容器的优势,实现了父子应用异步加载、子应用瞬间切换的能力,从而达到单应用项目的体验。

有关基于 iframe 的微前端框架 —— 擎天的更多相关文章

  1. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  2. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  3. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  4. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  5. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  6. ruby - sinatra 框架的 MVC 模式 - 2

    我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho

  7. ruby-on-rails - (Ruby,Rails) 基于角色的身份验证和用户管理...? - 2

    我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源

  8. ruby - 在 Rakefile 中动态生成 Rake 测试任务(基于现有的测试文件) - 2

    我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n

  9. ruby - 如何使用 Ruby 基于字母数字字符串生成颜色? - 2

    我想要像“嘿那里”这样的东西变成,例如,#316583。我希望将任意长度的字符串“归结”为十六进制颜色。我不知道从哪里开始。我在想,每个字符串的MD5散列都是不同的-但如何将该散列转换为十六进制颜色数字? 最佳答案 你可以只取几位前几位:require'digest/md5'color=Digest::MD5.hexdigest('Mytext')[0..5] 关于ruby-如何使用Ruby基于字母数字字符串生成颜色?,我们在StackOverflow上找到一个类似的问题:

  10. 【自动驾驶环境感知项目】——基于Paddle3D的点云障碍物检测 - 2

    文章目录1.自动驾驶实战:基于Paddle3D的点云障碍物检测1.1环境信息1.2准备点云数据1.3安装Paddle3D1.4模型训练1.5模型评估1.6模型导出1.7模型部署效果附录show_lidar_pred_on_image.py1.自动驾驶实战:基于Paddle3D的点云障碍物检测项目地址——自动驾驶实战:基于Paddle3D的点云障碍物检测课程地址——自动驾驶感知系统揭秘1.1环境信息硬件信息CPU:2核AI加速卡:v100总显存:16GB总内存:16GB总硬盘:100GB环境配置Python:3.7.4框架信息框架版本:PaddlePaddle2.4.0(项目默认框架版本为2.3

随机推荐