微前端是什么
简单来说 微前端是一个由多个应用组成的应用 或者 由多个微应用组成的一个主应用
换个角度来说:将庞大的整体拆成可控的小块。
微前端出现的背景
微服务关系
随着业务的发展,系统复杂度的上升,如何拆分大型项目的技术栈,进一步提高开发效率,以及成为行业的探索方向。 然后系统还是越来越复杂。随着业务的不断扩张,需求的不断新增,项目又慢慢的臃肿起来。于是推出微服务。
** 微服务注重解耦,微前端讲究聚合。 **
单项目出现的问题
- 单项目打包项目的等待时间长
- 技术栈定型后不好升级和更换
- 多个团队的协作,过于聚合,共同维护
- 各个业务相互影响,相互污染
- 历史项目旧逻辑拖累*
如果我希望:
- 不同团队都用不同的技术栈怎么办
- 希望每个团队都可以独立开发,独立部署怎么办?
- 希望抛弃原有历史代码包袱,使用新技术栈开发新的业务逻辑
以上问题的期望造就了微前端的
微前端架构
满足几个特性:
- 技术栈无关
2. 几个微应用可以使用不同的框架
- 微应用独立开发,独立部署
- 仓库独立,各自部署
- 微应用之间互不影响,稳定性高
4. 独立运行
- 每个微应用之间状态数据隔离,状态不共享
应用场景:
一般微前端还是应用在管理端,移动端应用比较少。
比如:公司内有多个业务部门,业务部门各自都有一个业务管理后台,而管理部门提出 1 个聚合功能需求,需要将各个部门的业务管理后台合并到一个总后台中,加载各个业务管理后台到总后台上。这时候有些管理后台是 vue2 有些是 vue3 有些是 react 有些是 angular,由于技术框架不同,迁移的人力成本和时间成本都会很高。
而如使用微前端架构,则可以利用微前端架构的特性去用较低成本去完成这个需求,将总后台作为主应用,每个业务管理后台作为一个微应用。
几种常见微前端方案
路由跳转分发
如图,在主应用中,通过 URL 跳转的方式进入到另一个微应用
即通过路由分发到不同的、独立前端应用上。
路由通常可以是应用框架自带的路由来实现,又或者通过服务器的反向代理实现。
iframe
可以创建一个全新的独立的宿主环境,可以友好的在主应用中展示微应用。
介绍一下iframe的通讯方式:
// parent
<body>
<iframe id="react_iframe" src="http://localhost:3000/"> </iframe>
</body>
<script>
// 父窗口传信息给子窗口
document.getElementById("react_iframe")
.contentWindow.postMessage("react", "http://localhost:3000/");
// 父窗口监听子窗口的信息
window.addEventListener("message", function (e) {
console.log("form_child_iframe", e);
});
</script>
// child src:http://localhost:8080/"
// 子窗口监听父窗口传递的信息
window.addEventListener("message", receivePerentMessage, false);
function receivePerentMessage(event) {
console.log("form_perent_window", event);
}
// 子窗口给父窗口传递信息
window.parent.postMessage("child", "*");
缺点:
- url 不同步。浏览器刷新时 iframe 数据状态会丢失、导航功能无法使用。
- UI 不协调。弹出一个遮罩弹窗只会在 iframe 内出现,Dom 的呈现不能跳出 iframe 的范围,如果要在浏览器居中弹出,需要通过 iframe 通讯在主应用中弹出兼容。
- 主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
- storage 也需要从微应用中透传出给主应用。
- 加载速度慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
基于缺点,使用 iframe 做微前端,需要考虑:
- 设计管理应用机制
- 需要考虑主应用与微应用的交互
- 窗口中的组件的加载、卸载
- 拷贝公用 UI 组件到主应用,比如:浏览器全屏居中弹窗
- 需要考虑主应用与微应用的交互
- 设计应用通讯机制
- 数据共享
- 响应式刷新
- 事件监听
微应用化
微应用化即在开发和运行时,应用都是以单一、微小应用的形式存在。
qiankun.js框架
qiankun:qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
qiankun.js的流程图
qiankun.js的通讯方式
// 在主应用中
// 主应用传递props属性给微应用
import { loadMicroApp } from 'qiankun';
microApp = loadMicroApp({
name: 'app1',
entry: '//localhost:1234',
container: this.containerRef.current,
props: { brand: 'qiankun' }, // 这里可以传递props属性给微应用
});
// 微应用中就获取到props中携带的brand数据
export async function mount(props) {
renderApp(props);
}
qiankun.js自带的状态管理工具
如果微应用和微应用之间需要进行共享数据状态那应该怎么做
// 在主应用中的入口文件中
import { initGlobalStates } from 'qiankun';
// 初始化 state
const state={}
const actions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
// 微应用中从生命周期 mount 中获取的props中拿到通信方法
export function mount(props) {
// 在当前应用监听全局状态,有变更触发 callback
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
// set状态API
props.setGlobalState(state);
}
qiankun.js原理
qiankun样式隔离
如果多个微应用同时挂载在一份document上,怎么实现的样式隔离
- shadowDOM 默认启用 strictStyleIsolation选项 子应用的根节点创建一个shadowRoot。最终整个应用的所有DOM将形成一棵shadow tree。我们知道,shadowDom的特点是,它内部所有节点的样式对树外面的节点无效,因此自然就实现了样式隔离。
- 缺点:UI框架的弹窗,如antd-modal元素直接挂在document.body,脱离了shadowTree,依然会污染全局。
- 给不同的子应用加入不同的前缀:less-loader、postcss-loader 的prefix功能,为不同的子应用样式加入属性值experimentalStyleIsolation选项
qiankun全局JS隔离、沙箱模式
表现:主应用和子应用能使用到同一份window的属性(主应用)。 主应用通过fetch HTML方式载入子应用,所以用的window是同一个window,所以多个子应用会有全局污染问题。
ProxySandBox 多实例[不会污染全局window]
- 遍历当前window的属性和方法创建一份window(fakeWindow)
- 通过Proxy劫持代理这份fakeWindow
- set:在fakeWindow上设值,并且记录变更
- get:window.window 或 window.self 或window.top 都指向poxy(防止穿透出去),从fakeWindow中去取值。
- 激活active和卸载inactive周期都不会做还原操作。
源码和注释:
- snapshotSanBox 不支持Poxy API的浏览器降级处理[会污染全局window]
- 主要是通过active激活子应用时记录通过遍历存储window备份当前window状态记录。
- 在卸载是通过快照编译还原window对象实。
源码和注释: