阿里云控制台前端框架-阿里云控制台前端框架
2016年,ThoughtWorks提出了一个类似于微服务的概念,叫做“Micro Frontend”。 此后,这个概念逐渐登陆web领域,各种微前端框架在前端技术领域层出不穷。 本文将为您介绍微前端的概念和意义,带您走近微前端框架,揭秘那些“不为人知”的巧妙技术实现。
概念
什么是微前端? 虽然它是在2016年提出的,但直到今天,我们只能描述它的轮廓,而不能给它一个明确的定义。 以下是笔者看过的关于微前端概念的一些阐述:
微前端是一个架构,不是一个独立的技术点。 我个人是从两个角度来看待微前端的,一个是应用结构,微前端是多个小应用聚合为一个的应用形态; 另一个是团队意识,在微前端架构下,每个团队只负责独立(封闭)),需要包含从服务端到客户端,团队合作的意识与以往大不相同。
微前端解决方案
如何从技术上实现微前端的概念? 在前端技术领域,出现了以下三种技术方案:
这三种选择各有优缺点,我们不能立即断定哪个更好。
节目类型
典型技术
优势
缺点
共同点
接口协议
单温泉
比较免费,可以独立打包
不能满足很多场景
沙盒隔离
乾坤
简单直接的开发思路
沙箱引起的性能和其他问题
模块协议
webpack 模块联合
用模块化思维理解引用
没有构建工具无法使用
就目前的市场情况来看阿里云控制台前端框架,基于沙盒隔离的微前端解决方案占据主导地位,即本文将要阐述的微前端框架也是此类解决方案。 其中,笔者认为最重要的一点是,基于沙箱隔离的方案,可以让应用以最低的成本从原来的单体大应用迁移到微前端架构。
微前端框架的比较评估
微前端框架是用于快速将网站或其他技术栈切换到微前端架构的底层引擎。 市场上有许多微前端框架。 作者在2021年做了一个合集,比较典型。 (虽然后来出现了更新的微前端框架,但大部分原理都是一样的,所以下面的框架就足够了。)
除了webpack的federal module方案需要结合构建比较特殊,其他方案都是在运行时完成应用聚合。
“子应用独立运行”是指子应用可以不放置在基础应用环境中自行运行,便于不同基础的调试和引入。
“子应用嵌套子应用”是一个比较特殊的点,目前市面上能做到的框架并不多。
微前端框架核心技术
在微前端架构中,有“主应用”和“子应用”两个层次,微前端框架的主要任务是让子应用在主应用中有效运行。 如上所述,目前很多微前端框架都是基于(或支持)沙箱隔离实现主从应用运行机制。 笔者实现的小微前端框架“麦饭”也属于此类。 因此,本文只深入讲解这类微前端框架的技术原理和实现。 微前端框架要解决的核心问题是资源加载和环境隔离。 此外,还有路由、通信等问题。
资源加载
微前端框架需要从服务端拉取子应用的代码文件,完成子应用的解析挂载和运行。 除了webpack的模块联合方案阿里云控制台前端框架,现在常见的方案有两种,即:以JS文件为入口; 使用 HTML 文件作为入口点。 以JS文件为入口,可以直接运行JS脚本获取JS导出的内容,但这种方式只能加载脚本资源,无法加载CSS等样式资源。 以HTML文件为入口,通过HTML文件中的文件引用,可以一起加载所有对应的JS和CSS文件。 而且,网站使用HTML文件作为入口,正好可以开发子应用。作者是按照web开发的思路来编写子应用的。
作者在写麦饭框架的时候,希望子应用可以直接运行,所以使用了HTML作为入口文件。 开发人员使用特殊的 importSource 函数来导入入口文件。 该函数可以根据入口文件解析并缓存子应用的所有资源。
解析资源
框架获取HTML入口文件地址后,通过HTTP请求获取文件内容,并解析内容。 解析时需要解析资源树,即通过HTML读取所有资源文件,如link、script[src]。 在阅读资源的时候,可能还需要阅读资源本身引入的资源。 大体逻辑如下:
在解析过程中,还需要根据registerMicroApp(麦饭提供的注册接口)的配置来决定如何处理CSS规则。 解析获取CSS的技巧就是通过.sheet读取CSSStyleSheet对象,从中提取出所有的CSS样式规则,然后根据配置逻辑生成最终的样式规则。
预加载/延迟加载
在设计上,子应用程序的资源可以以两种可选的形式加载。 在麦饭中,如果想提前预加载子应用资源,可以在注册微应用时直接传入importSource(…)。 一旦执行此函数,它将请求资源并缓存它们。 但是,如果你不需要预加载,而想在子应用需要进入界面时(或者你打算让子应用进入界面时)加载资源,那么configure() => importSource (...),这个配置只会在子应用程序执行bootstrap时才请求资源。
环境隔离
环境隔离是微前端框架实现的核心技术难点。 由于子应用的开发团队是分开的,因此两个子应用之间可能存在相互污染的问题。 这就需要微前端框架实现一种能力,让子应用在自己隔离的环境中运行,不至于干扰其他子应用。 应用造成污染。 目前可用于解决环境隔离的方案有:
还有一些框架将这些解决方案结合起来,在不同的场景中主动或被动地使用其中的一种。 其中,快照沙箱和代理沙箱是两种比较独特的技术方案:
快照沙箱
多个子应用在页面上相互切换,子应用脚本的运行会污染当前全局环境。 快照沙箱就是用来解决这种污染的。
该方案只适用于一次只运行一个子应用的场景,比如腾讯云控制台。 当子应用程序进入界面时,对窗口上的所有属性进行快照。 子应用程序运行时可以修改窗口。 当子应用程序离开界面时,清理窗口,然后将快照上的属性重新添加到窗口,恢复子应用程序挂载前的窗口。
代理沙箱
代理沙箱解决了一个页面同时运行多个子应用的场景。 这分两步完成:
1.创建代理对象
例如,上面提到的窗户可能被污染了。 然后创建一个窗口代理对象,比如fakeWin,如下:
经过这样的处理,我们在读取的时候可能会读取到原来窗口上的值,但是一旦我们写入新的属性,就会读取到刚刚写入的值,但是对于原来的窗口来说,是没有被污染的。
2.创建运行沙箱
要将代理对象用作子应用程序脚本的全局对象,子应用程序必须在沙箱中运行。 这个沙箱使用我们做的代理对象作为全局变量,这样子应用的脚本就会去操作这个代理对象。 这样就起到了代理隔离其他子应用的作用。 具体实现如下:
上面代码中的window、document、location等都是前面创建的代理对象。
当然,这里只给出了一些最核心思想的代码。 其实在实际实现的时候需要考虑各种特殊情况,需要很多方面的处理。
通过代理沙箱,子应用可以在主应用中独立运行,不会对主应用上的其他子应用产生负面影响。 不过值得一提的是,由于代理沙箱实际上虚拟了一个子应用运行的环境,意味着需要消耗更多的计算资源,这会对子应用的性能造成一定的影响。 同时,由于这个虚拟环境在某些情况下必须连接到真实环境才能运行,或者另一方面,虚拟环境可能无法提供子应用程序所需的所有依赖,这将导致to 一些功能失效,甚至影响整个子应用的性能。
路线地图
如果子应用有自己的路由系统,如果处理不好,子应用在切换路由时会污染父应用,导致浏览器url改变,导致当前页面切换到另一个地方。 为了解决这个问题,麦饭实现了路由映射功能。 由于子应用运行在沙箱中,不同层的应用获取到的位置是不同的。 基本应用程序使用浏览器的位置,但其子应用程序不使用。 修改浏览器的url后,可以通过路由映射机制伪造子应用获取到的url。 具体实现是创建一个临时的iframe,利用代理沙箱的能力,将子应用的位置代理到iframe中的位置。
得益于代理沙箱,子应用的url变化不会引起浏览器的url变化。
映射逻辑需要写一个map和reactive配置项。 当浏览器的URL发生变化时,通过map映射到子应用。 当子应用内部url发生变化时,通过reactive映射到浏览器,这样即使用户某一时刻刷新浏览器,也可以通过url映射准确还原子应用当前界面关系。
山
在麦饭中,子应用需要使用标签来判断子应用挂载到哪里。 与qiankun等框架不同,qiankun需要在子应用中确定挂载点,但这可能会引起冲突。 麦饭的理念是子应用开发团队不应该考虑自己应用的外部环境。 因此,子应用程序的挂载位置应该由父应用程序决定。
子应用程序被放置在 中,赋予开发者一些特殊的能力:
在实现中使用了一些 hack 技术。 比如需要使用节点所在作用域的顶级节点,在顶级节点DOM对象上挂载一些数据。 通过这个技巧,可以保证在节点被移除和挂载回来时,能够正确的恢复到之前的界面。
keepAlive的意思是子应用在节点没有被移除的情况下执行unmount时,实际上并没有销毁子应用构建的DOM树,而是放到了内存中。 子应用再次挂载时,直接将内存中的DOM树挂载在内部。
通信/应用程序树
这部分是麦饭设计最复杂的部分,也是它最终区别于其他微前端框架的地方。
我构建了这样一个树状的数据结构,我称之为“应用树”。 表达了基于MFY开发的微前端应用中子应用的引用关系。
范围
作用域概念是指一个应用程序启动后,会创建一个作用域(scope)。 这个作用域保存了应用程序的一些运行时信息,同时传递了通信的接口方法。 一个应用程序可能有多个子应用程序,这些子应用程序有自己的作用域。 通过作用域可以完成上下层应用程序之间的通信。 例如,parent_app 可以向 child_app_1 和 child_app_2 发送指令。 两个子应用收到这条指令后,各自实现自己的逻辑。 child_app_2可以向parent_app发送命令,parent_app将命令转发给child_app_1,从而完成两个子应用之间的通信。 这与 React 组件通过 props 传递数据的模式非常相似。
rootScope是一个特殊的作用域,对应于基础应用,是应用树的顶点。 由于我将作用域设计为可以广播消息的订阅/发布对象,因此跨层应用程序之间的直接通信可以使用 rootScope 完成(尽管不推荐)。
连接范围
每个应用程序都通过 connectScope 连接到自己的范围。 需要一些技巧来实现这一点。 在同一层,实现逻辑有点像react hooks。 您无需关心您在应用程序树中的位置。 对于子应用开发团队,只需要在代码中使用connectScope()函数就可以直接连接到自己的scope。 如果你实现过 React Hooks,你应该能够理解它的一个实现原理。 但是,由于某些实现限制,您不能异步执行 connectScope。 代码第一次执行时必须同步调用connectScope获取当前子应用的作用域。
状态共享
“如果子应用1修改了用户的某个状态,子应用2如何响应这个修改?”
这个问题涉及一个状态共享问题。 由于我在设计时坚持每个子应用团队应该封闭开发的理念,所以开发团队不要考虑自己开发的应用会和其他应用一起使用,或者需要依赖状态其他应用的变化,这会让我在开发过程中,一直处于当前应用状态的未知状态,无法调试和测试。 因此,我直接拒绝在设计中实现子应用之间的状态共享。
但是在实际使用中,这个要求是存在的。 因此,我建议使用通信方式来解决问题。 子应用1通过rootScope发送消息通知网络我已经改变了用户状态。 其他子应用收到这个消息后,决定是否重新渲染界面。
思考
本文虽然通过作者实现了麦饭微前端的小框架,详细讲解了一个微前端框架的核心技术实现,但同时也存在很多问题:
微前端不是万能的,陷阱也很多,所以应遵循“没有银弹”的说法。
结语
微前端是一种架构形式。 一旦采用这种架构,它将影响您的应用程序的运行方式、您的团队的管理方式以及您构建和部署它的方式。 因此,开发团队最好经过长时间的研究。 决定启用此架构。 从这篇文章中,你也会发现,想要实现微前端框架的核心能力,需要使用一些看起来并不优雅的hack方法。 既然是hack方式,也有一定的弊端,更容易为以后的开发埋坑。 . 本文仅介绍实现微前端框架的核心技术点。 在实际项目中,需要面对更多的问题,但这并不是说我要劝阻大家,而是希望大家在选择的时候,能够根据实际需求来做决定。 不要马上使用它,因为它太热了。 如果你对微前端相关的话题感兴趣,可以在文章下方留言,一起探讨微前端框架的实现技术。