微前端是什么
微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为
多个小型前端应用聚合为一的应用
。各个前端应用还可以
独立运行
、
独立开发
、
独立部署
。
简单来说,就是利用一系列工具和技术,将各个团队的UI页面 组装成用户可以连贯的应用程序。
后端解耦,前端聚合
采用微服务的原因主要还是在于,
使用微服务架构来解耦服务间依赖
。
而在前端微服务化上,则是恰恰与之相反的,人们更想要的结果是
聚合
,尤其是那些 To B(to Bussiness)的应用。
在这两三年里,移动应用出现了一种趋势,用户不想装那么多应用了。而往往一家大的商业公司,会提供一系列的应用。这些应用也从某种程度上,反应了这家公司的组织架构。然而,在用户的眼里他们就是一家公司,他们就只应该有一个产品。相似的,这种趋势也在桌面 Web 出现。
聚合
成为了一个技术趋势,体现在前端的聚合就是微服务化架构。
目的
优点
缺点
各个团队需要建立维护自己的服务器,构建流程和持续集成的管道,可能还加载冗余的js/css
后端团队有独立的数据库,团队之间需要定期复制数据,一旦出现错误,容易引起数据不一致
微前端实施方式
路由分发式微前端
通过路由将不同的业务
分发到不同的、独立前端应用
上。其通常可以通过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。
http { server { listen 80; server_name www.phodal.com; location /api/ { proxy_pass http://http://172.31.25.15:8000/api; } location /web/admin { proxy_pass http://172.31.25.29/web/admin; } location /web/notifications { proxy_pass http://172.31.25.27/web/notifications; } location / { proxy_pass /; } } }
iframe
顾名思义,通过iframe加载子应用。通信可以通过postMessage进行通信。
优点
缺点
url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
ajax
ajax 请求服务端,直接在主页面区域返回拼装好的html
优点
缺点
web component
将前端应用程序分解为自定义 HTML 元素。基于CustomEvent实现通信
Shadow DOM天生的作用域隔离
重写现有的前端应用,使用 Web Components 来完成整个系统的功能。
single-spa
实现一套生命周期,在 load 时加载子 app,由开发者自己玩,别的生命周期里要干嘛的,还是由开发者造的子应用自己玩
监听 url 的变化,url 变化时,会使得某个子 app 变成 active 状态,然后走整套生命周期
子应用最关键的一步就是导出 bootstrap, mount, unmount 三个生命周期钩子。
基于浏览器原生的事件系统,无框架耦合,全局开箱可用。
把多个应用的运行时集成起来需要项目间自行处理内存泄漏,样式污染问题
qiankun
qiankun基于single-spa进行了二次开发
子应用:与single-spa基本一致,导出了三个生命周期函数。
Proxy沙箱,它将window上的所有属性遍历拷贝生成一个新的fakeWindow对象,紧接着使用proxy代理这个fakeWindow,用户对window操作全部被拦截下来,只作用于在这个fakeWindow之上
ShadowDOM样式沙箱会被开启。在这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响。
Scoped CSS,qiankun会遍历子应用中所有的CSS选择器,通过对选择器前缀添加一个固定的带有该子应用标识的属性选择器的方式来限制其生效范围,从而避免子应用间、主应用与子应用的样式相互污染。
但如果用户在运行时引入了新的外联样式或者自行创建了新的内联标签,那么qiankun并不会做出反应
qiankun在框架内部预先设计实现了完善的发布订阅模式
无界
使用iframe有三个难以解决的问题,
路由状态丢失
,刷新一下,iframe的url状态就丢失了
dom割裂严重
,弹窗只能在iframe内部展示,无法覆盖全局
通信非常困难
,只能通过postmessage传递序列化的消息
无界微前端框架通过继承iframe的优点,解决iframe的缺点,打造一个接近完美的iframe方案
在应用A中构造一个shadow和iframe,然后将应用B的html写入shadow中,js运行在iframe中,
注意
iframe
的
url
,iframe保持和主应用同域但是保留子应用的路径信息,这样子应用的js可以运行在iframe的location和history中保持路由正确。
在iframe中拦截document对象,统一将dom指向shadowRoot,此时比如新建元素、弹窗或者冒泡组件就可以正常约束在shadowRoot内部。
dom割裂严重的问题,主应用提供一个容器给到shadowRoot插拔,shadowRoot内部的弹窗也就可以覆盖到整个应用A
路由状态丢失的问题,浏览器的前进后退可以天然的作用到iframe上,此时监听iframe的路由变化并同步到主应用,如果刷新浏览器,就可以从url读回保存的路由
通信非常困难的问题,iframe和主应用是同域的,天然的共享内存通信,而且无界提供了一个去中心化的事件机制
iframe+ web component
Shadow DOM 是 Web Components 技术的一部分,它允许开发者创建封装、可复用的组件。当一个元素使用 Shadow DOM 创建时,它会包含一个 Shadow Root,这是一个独立的 DOM 子树,与文档中的其他部分相互隔离,可以在其中定义和控制样式和行为。因此,Shadow DOM 的插拔机制也是非常重要的。
在 Shadow DOM 中,插入和移除节点的过程称为“插拔”。Shadow DOM 提供了以下方法来实现插拔:
attachShadow(options) 方法:该方法将返回一个 ShadowRoot 对象,通过该对象可以管理 Shadow DOM 的内容。
appendChild(node) 和 removeChild(node) 方法:这些方法允许向 Shadow DOM 中添加或删除节点。
MutationObserver API:使用该API,可以监视 DOM 树的变化,并在变化发生时采取适当的行动。
需要注意的是,在 Shadow DOM 中,被插入到 Shadow Root 中的元素有可能难以再次获取或操作,因为它们可能不会出现在文档的正常 DOM 树中。为了解决这个问题,我们可以使用 getElementById() 或 querySelector() 等方法,或者在创建自定义元素时定义自定义方法。
渲染子应用步骤
因为 iframe 的 src 要设置为主应用的域名,继续请求资源会失败
处理css 重新注入html (有插件系统,可以对子应用的css定义)
CSS 由于在 shadowDOM 内,样式也不会影响到外部,也不会受外部样式影响。
创建
script
标签,并插入到 iframe 的 head 中
对 iframe 的 document.querySelector 进行改造,需要劫持 document
改为从
shadowRoot
里面查找
,才能
使 Vue 组件能够正确找到挂载点
micro app
micro-app并没有沿袭single-spa的思路,而是借鉴了WebComponent的思想,通过CustomElement结合自定义的ShadowDom,将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染。并且由于自定义ShadowDom的隔离特性,micro-app不需要像single-spa和qiankun一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置,是目前市面上接入微前端成本最低的方案。、
它在
基座应用
和
子应用
之间充当桥梁胶水的作用。
接入方式
import microApp from '@micro-zoe/micro-app' ; microApp.start();export function MyPage () { return ( <div > <h1 > 子应用h1 > <micro-app name ='app1' // name (必传):应用名称 url ='http://localhost:3000/' // url (必传):应用地址,会被自动补全为http: //localhost:3000 /index.html baseroute ='/my-page' // baseroute (可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page ` >micro-app > div > ) }
加载子应用过程
microApp.start() 后,会注册一个名为 micro-app 的自定义
webComponent
标签。
通过 fetch 拿到 url 对应的 html 字符串,然后替换 head 和 body 标签为自定义标签,避免污染主应用
micro-app-head micro-app-body
htmlstr.replace(/, ') htmlstr.replace(/, ')
处理link标签
处理 href 属性,在原本的 href 的前面拼接上 app.url ,相对路径改绝对路径
若为样式链接,ref 的属性是 stylesheet,删除该link,记录href内容,创建一个style标签插入
创建style标签时会,给子应用的style标签添加作用域,实现样式隔离
处理style 标签
给子应用的 style 标签加上作用域,前缀是 ${microApp.tagName}[name=xxx]
例如:.test