草庐IT

基于微前端的业务逻辑拆分

移动Labs 2023-03-28 原文

一、什么是微前端?

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

微前端在2016年ThoughtWorks Technology Radar正式被提出。借鉴了微服务的架构理念,将一个庞大的前端应用拆分为多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用联合为一个完整的应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。

架构特点

  • 技术栈无关:主框架不限制接入应用的技术栈,子应用可自主选择技术栈;
  • 独立开发/部署:各个团队之间仓库独立,单独部署,互不依赖;
  • 增量升级:当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性;
  • 独立运行时:微应用之间运行时互不依赖,有独立的状态管理;
  • 提升效率:应用越庞大,越难以维护,协作效率越低下。微应用可以很好拆分,提升效率。

二、目前可用的微前端方案

2.1 基于iframe完全隔离

作为前端开发,我们对iframe已经非常熟悉了,在一个应用中可以独立运行另一个应用。

优点:

  • 非常简单,无需任何改造;
  • 完美隔离,JS、CSS都是独立的运行环境;
  • 不限制使用,页面上可以放多个iframe来组合业务。
缺点:

  • 无法保持路由状态,刷新后路由状态就丢失;
  • 完全的隔离导致与子应用的交互变得极其困难,只能采用postMessage方式;
  • iframe中的弹窗无法突破其本身;
  • 整个应用全量资源加载,加载太慢。

2.2 基于single-spa路由劫持

single-spa (opens new window)是一个目前主流的微前端采用基座技术方案,其主要实现思路:预先注册子应用,监听路由的变化,匹配到激活的路由则加载子应用资源,顺序调用生命周期函数(初始化,挂载,卸载)并最终渲染到容器。

优点:

  • 有开箱即用的API;
  • 技术栈无关,任意技术栈均可接入;
  • HTML Entry接入方式,将整个微应用打包成一个JS文件,发布静态资源服务器,然后在主应用中配置该JS文件的地址告诉single-spa去这个地址加载微应用。
缺点:

  • 对微应用侵入性强,会将应用打包为一个JS文件,常用的优化措施,如按需加载,css独立打包等都没有了;
  • 没有做样式隔离,样式容易混淆覆盖;
  • 没有做JS隔离,JS全局对象污染;
  • 无预加载机制,加载资源太慢;
  • 微应用之间没有任何通信手段,只能用户自己实现。

2.3 基于web components封装组件

官方提出的组件化方案,它通过对组件进行更高程度的封装,以组件加载的方式将微应用整合在一起实现应用之间的解耦。

优点:

  • 加载子应用比single-spa注册监听方式更加优雅;
  • 技术栈无关,是浏览器原生组件,在任何框架中都可以使用;
  • 无需与其他应用之间产生任何关联;
  • 应用间采用shadow dom,隔离样式。
缺点:

  • 浏览器兼容性不好。

2.4 quankun

Quankun是对single-spa做了一层封装,主要解决了single-spa的痛点和不足,通过import-html-entry包解析HTML获取资源路径,然后对资源进行解析,加载。

优点:

  • 阿里团队开发维护,文档多,有开箱即用的API;
  • JS沙箱机制,确保应用直接变量事件不冲突;
  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速了微应用的打开速度;
  • 可用于任意JS框架,像嵌入一个iframe,且兼容IE11。
缺点:

  • 上线部署文档少;
  • 只能解决子项目之间的样式相互污染,不能解决子项目的样式污染主项目的样式。

2.5 EMP

主要基于Webpack5 Module Federation,是一种去中心化的微前端实现方案,不仅能很好的隔离应用,还可以轻松实现应用之间的资源共享和通信。

优点:

  • 快速封装可复用模块,无需单独拆包发布到NPM,可直接暴露需要共享的模块;
  • 实时动态更新,只需要发布基站应用,只需要访问时刷新,即可使用最新业务模块;
  • 引入端无需手动更新,远端模块灵活维护和引入端可以自由组合,甚至可以运行是引入使用远端模块;
  • 做到第三方依赖共享,使代码尽可能地重复利用,减少加载内容。
缺点:

  • 对webpack强依赖,老旧项目不友好;
  • 没有做CSS隔离和JS隔离,需要靠用户自觉;
  • 子应用保活、多应用激活无法实现;
  • 主、子应用的路由可能发生冲突。

三、我们要解决的问题

拆分巨型应用,使应用方便迭代更新,提高开发部署效率

单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。而微前端思想可以将一个庞大的前端应用拆分为多个独立灵活的小应用,每个应用可以独立开发,独立更新,独立部署,减少模块之间的耦合性,提高项目扩展性,有利于各子应用渐进式优化,不影响新增功能以及其他子应用的运行。

跨团队协作,实现需求分工,争取资源最优化,提高工作效率

大需求需要拆分,不同的部分需要不同的业务能力,需要涉及多个团队甚至多个部门协同开发,所谓专业的团队做专业的事情,细化能力,实现能力效用最大化,提高团队整体工作效率,实现1+1>2的效果。

兼容历史应用,实现增量开发

旧项目技术栈不统一,微前端主框架可以不限制接入应用的技术栈,可以随时加载不同技术栈模块,不需要为了兼容现有项目做大规模改造重构,提升开发,测试与维护效率。

四、我们的方案

1️⃣采用基于web component的micro-app框架为基础,拆分出可独立运行的子应用;

2️⃣进一步封装业务逻辑层,实现基于js沙箱与shadow dom的代码隔离;

3️⃣抽离子应用的公用模块,进一步缩减巨石项目代码规模,提高迭代维护效率。

4.1 技术特点

高扩展性、独立性、开放性:基于micro-app实现微前端主框架,将主应用拆分为主平台系统和各子系统等可以独立交付运行的前端子应用,再进一步将业务模块抽象,公用模块从子应用中抽离,模块化开发部署,提高了各个子应用/业务模块的可拓展性,减少耦合性,提高开发效率,降低交付成本。

统一功能模块独立性,提高安全性:用户信息,角色权限等通用业务功可以抽象出来单独开发部署,降低重复开发,减少项目整理代码量,但因为需要集成到不同的子应用,为避免和宿主页面样式冲突,采用基于shadow dom+JS沙箱机制,建立独立作用域,实现custom element,将JS和CSS都被隔绝在dom作用域中,实现代码隔离,使代码更干净,整洁,降低耦合性。

抽离通用功能模块,进一步缩减项目冗余:菜单,头部,脚部,登录,注册等使用vite工具将其打包微ESM模块,对于当前单一技术栈实现的业务项目,不需要每个子应该都重新开发公用模块,让代码包总体积更小,提高开发效率,并实现模块的按需异步加载,提升页面整体加载速度。

4.2 什么是JS沙箱

JS沙箱主要是用于隔离挂载在window上的变量,保证应用之间js环境得独立。在子应用运行时访问的window其实是一个Proxy代理对象。所有子应用的全局变量变更都是在闭包中产生的,不会真正回写到window上,这样就能避免多实例之间的污染。

4.3 什么是shadow dom

Shadow-dom是游离在DOM树之外的节点树,他的创建基于自定义DOM元素(非document),并且创建后的shadow-dom节点可以从界面上直观的看到。更重要的是,shadow-dom具有良好的密封性,可以隔离css样式,避免css全局样式污染。

4.4 模块之间如何通信

该方案涉及按照业务拆分的子应用,按照功能拆分的子模块,他们的通信方式是不通的。

1.按照业务拆分的子应用并不是一个模块,而是一个可以独立运行的应用,然而应用之间的频繁的通信会增加应用的复杂度和耦合度,因此要尽量减少子应用之间的通信,非必要不交互。这里我们使用micro-app官方提供的API即可实现。

2.按照功能模块拆分的子模块,既是基于web component实现的子模块,之间的通信可遵循父组件加载子组件,父子组件之间的通信。

有关基于微前端的业务逻辑拆分的更多相关文章

  1. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  2. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

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

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

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

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

  5. 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

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

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

  7. ruby - 拆分字符串并分配给不同的变量 - 2

    我从ui中得到日期范围为-approved_between"=>"2013-03-17-2013-03-18"我需要拆分此approved_start_date="2013-03-17"和approved_end_date="2013-03-18"...我希望使用它在mysql中查询,因为mysql中的日期格式是created_at:2012-07-2810:35:01.我正在做的是:approved=approved_between.split("")approved_start_date=approved[0]approved_end_date=approved[2]很确定这不是处

  8. ruby-on-rails - Ruby on Rails 将列表拆分或切片为列 - 2

    @locations=Location.all#currentlistingall@locations=Location.slice(5)orLocation.split(5)使用Ruby,我试图将我的列表分成4列,每列限制为5个;然而,切片或拆分似乎都不起作用。知道我可能做错了什么吗?任何帮助是极大的赞赏。 最佳答案 您可能想使用in_groups_of:http://railscasts.com/episodes/28-in-groups-of这是RyanBates在railscast中的示例用法:

  9. 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

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

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

随机推荐