什么是微前端
将不同的功能按照不同的维度拆分多个子应用,通过主应用来加载这些子应用。 微前端的核心在于拆,拆完后再合
为什么要去用他?
- 不同的团队开发同一个应用,使用不同的技术栈?
- 每个团队都可以独立开发,独立部署
- 项目中一些老的应用代码嵌进来
需要系统中的部件具备足够清晰的服务边界。
微前端的实现思想就是将一个应用划分成若干个子应用,将子应用打包成一个个的 lib。当路径切换 时加载不同的子应用。这样每个子应用都是独立的,技术栈也不用做限制了!从而解决了前 端协同开发问题。
目前主流的微前端框架
- Single-SPA
single-spa 是一个用于前端微服务化的 JavaScript 前端解决方案 (本身没有处理样式隔离, js 执行隔离) 实现了路由劫持和应用加载 - qiankun
基于 Single-SPA, 提供了更加开箱即用的 API ( single-spa + sandbox+ import-html-entry ) 做到了,技术栈无关、并且接入简单(像 iframe 一样简单)
总结:子应用可以独立构建,运行时动态加载,主子应用完全解耦,技术栈无关,靠的是协议接入(子应用必须导出 bootstrap、mount、unmount 方法)
iframe 中的子应用切换路由时用户刷新页面就回去了
应用通信:基于 URL 来进行数据传递,但是传递消息能力弱(最简单)、基于 props 主子应用间通信、使用全局变量、 Redux 进行通信
相比于 single-spa,qiankun 解决了 JS 沙盒环境,不需要我们自己去进行处理。在 single-spa 的开发过程中,我们需要自己手动的去写调用子应用 JS 的方法(如上面的 createScript 方法),而 qiankun 不需要,乾坤只需要传入响应的 apps 的配置即可,会帮助我们去加载。还有一个就是他们本质的区别
微前端原理
微前端三个问题:
- 子应用如何定义和使用?
关键 api 是 registerApplication,这里需要传入 app、activeWhen(会监听路由变化并劫持更改原生方法,应用路由判断逻辑) - 如何动态加载?
import-html-entry - 如何隔离?
样式隔离通过 shadow dom,内部所有节点的样式对树外面的节点无效,因此自然就实现了样式隔离。
有个 js 沙箱机制,沙箱创造一个干净的环境给子应用使用,当切换时,可以选择丢弃属性和恢复属性
组合式应用路由分发。通过父类对路由的检测,动态的对子应用进行卸载或挂载。
浏览器首次打开父类应用的时候,第一步就是先使用调用 registerApplication 注册 app,接下来判断当前的路由是属于哪一个子应用的,他的判断依据就是 apps 中的 activeWhen,接下来就会将当前的子应用划分状态,appToLoad,appToUnmounted, appToMounted。接下来根据子应用的状态,先去执行需要卸载的子应用,卸载完成之后,就会去执行状态为 appToLoad, appToMounted 的子应用,那么在最后在执行相应的回调函数,也就是我们在子应用中注册的那些生命周期。 https://blog.51cto.com/palxp/5712485
性能优化:
- 预加载
微应用的加载实际并不是越早越好,要在主应用完成加载和渲染之后的空闲时间,再启动微应用的加载。毕竟浏览器的请求并发存在限制,且 qiankun 是通过解析 HTML 模板,重构 XHR 的 HTTP 请求,来进行 JS 和 CSS 资源的请求,其 HTTP 的请求压力就更大了。
因此选择在 Layout 组件挂载之后进行预加载。
qiankun 预加载的属性是 prefetch,默认为 true。
- 配置为 true 则会在第一个微应用 mount 完成后开始预加载其他微应用的静态资源
- 配置为 ‘all’ 则主应用 start 后即开始预加载所有微应用静态资源
- 可以配置成数组的形式,指定加载哪些微应用 因此设置为’all’
- 依赖库共享
官方推荐方案 webpack 的 externals 能够将一些第三方包通过外部扩展的方式,通过 CDN 的方式引入到项目中,使用 CDN 加载第三方库的数量不宜过多,过多的 CDN 请求,会占据浏览器并发请求数,导致父应用加载速度变慢。 - 请求数据共享 页面需要后台数据到达之后才能渲染对用户有意义的页面。initGlobalState(state)定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法。
- 优化 qiankun 执行性能
with(作用在于改变作用域。强制所有子应用的 js 的作用域都是同一作用域。)语句执行带来性能问题,主要是为了 document, location 去 proxy window 上找,这样就可以使用自定义的 document window。 但是 with 的问题就是会阻止代码执行时候的优化。github 上有个方法是像沙盒可以提供独立的 window 实例一样,提供独立的 document 实例。
2.8 之后增加了性能加载模式,通过{ sandbox: { speedy: true } }开启,在保留沙箱的模式基础上做了 window 和 document 实例的性能优化。
iframe 问题
iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。
- url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
- UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
- 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
- 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
其中有的问题比较好解决(问题 1),有的问题我们可以睁一只眼闭一只眼(问题 4),但有的问题我们则很难解决(问题 3)甚至无法解决(问题 2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。
iframe 与微前端对比
iframe(无界兼容微前端)
1.改造快,对所有跳转做同意拦截处理,保证 URL 同步刷新能正常进退。
2.原生隔离,效果好
3.测试回归只需要针对 url 跳转重点验证
iframe 的 src 和主应用的域名是一致的,也就是说子应用运行的 iframe 沙箱和主应用是同域的,对于同域的 iframe,可以共享内存通信了无需 postMessage 这种方式
弊端:弹窗居中(手动移动可优化)、页面跳转 url 不会同步,前进后退需要特殊处理(iframe 页面内部的跳转虽然不会让浏览器地址栏发生变化,但是却会产生一个看不见的“history 记录”,也就是点击前进或后退按钮(history.forward()或 history.back())可以让 iframe 页面也前进后退,但是地址栏无任何变化。)
适合:没有弹窗、浮层、高度固定(不固定则要动态计算 iframe 的内容高度赋值给 iframe)的纯信息展示页
弊端
- 改造慢,所有接口都要改成支持跨域、绝对路径,所有页面都需要二次改造
- 隔离有兼容问题
- 所有页面重新测试回归
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付
