小明为公司重构了一版新的管理后台,采用了市面上最流行的SPA渲染模式,具体技术栈使用的是react+react-router。
项目第一版很快就顺利上线了,但在后续的迭代中,遇到一个棘手的问题:产品经理希望快速复用之前项目的某些页面。这让小明犯了难,因为老项目是用“上古神器”jQuery写的,完全重构成react,成本非常高。这时后端老哥丢过来一句:“你们前端用iframe嵌进来就可以了吧?”小明心里很清楚iframe有许多小毛病,但在当时,也确实没有比它更好的选择了。
小明心里其实非常清楚,这一切都是iframe带来的弊端。
市面上对微前端的定义让人眼花缭乱,比如微前端是:
这里给出我对微前端最接地气的定义:
首先,“微前端”作为近几年国内前端界最火的技术之一,目前存在多个技术流派。我按照它们对iframe看法的不同,将主流微前端方案分为了三大派系:革新派、改良派、中间派。
为了确保子应用之间样式互不影响,qiankun内置了三种样式隔离模式:
可通过strictStyleIsolation:true开启。原理是利用webComponent的shadowDOM实现。但它的问题在于隔离效果太好了,在目前的前端生态中有点水土不服,这里举两个例子。
解决方案:调整antd入参,让其在当前位置渲染。
确保子应用之间的“全局变量”不会产生冲突。
与快照沙箱思路很相似,但它不用通过Diff前后window的方式去记录变更,而是通过ES6的Proxy代理window属性的set操作来记录变更。由于不用反复遍历window,所以性能要比快照沙箱好。
以上两种沙箱机制,都只支持单例模式(同一页面只支持渲染单个子应用)。
原因是:它们都直接操作的是全局唯一的window。此时机智的你肯定想到了,假如为每个子应用都分配一个独立的“虚拟window”,当子应用操作window时,其实是在各自的“虚拟window”上操作,不就可以实现多实例共存了?事实上,qiankun确实也是这样做的。
既然是“代理”沙箱,那“代理”在这的作用是什么呢?
主要是为了实现对全局对象属性get、set的两级查找,优先使用fakeWindow,特殊情况(set命中白名单或者get到原生属性)才会改变全局真实window。
如此,qiankun就对子应用中全局变量的get、set都实现了管控与隔离。
2019年开源,是国内最早流行起来的微前端框架,在蚂蚁内外都有丰富的应用,后期维护性是可预测的。
由上可知,代理沙箱实现的关键是需要将子应用的window“替换”为fakeWindow,在这一步qiankun是通过函数window同名参数+with作用域绑定的方式,更改子应用window指向为fakeWindow,最终使用eval(...)解析运行子应用的代码。
constjsCode=`(function(window,self,globalThis){with(this){//yourcodewindow.a=1;b=2... }}).bind(window.proxy)(window.proxy,window.proxy,window.proxy);`eval(jsCode)问题就出在这个eval上,vite的构建产物如果不做特殊降级,默认打包出的就是ESModule语法的代码,使用eval解析运行会报下图这个错误。
所以这款插件的解决方案就是替换子应用代码中的静态import为动态import(),以绕过上述限制。
那DOM就不放iframe里渲染了,而是单独提取到一个webComponent里渲染,顺便用shadowDOM解决样式隔离的问题。
简单说,无界的方案就是:JS放iframe里运行,DOM放webComponent渲染。
那么问题来了:用JS操作DOM时,两者如何联系起来呢?毕竟JS默认操作的总是全局的DOM。无界在此处用了一种比较hack的方式:代理子应用中所有的DOM操作,比如将document下的getElementById、querySelector、querySelectorAll、head、body等查询类api全部代理到webComponent。
下图是子应用真实运行时的例子:
至于多实例模式,就更容易理解了。给每个子应用都分配一套iframe+webComponent的组合,就可以实现相互之间的隔离了!
通过重写iframe实例的history.pushState和history.replaceState,将子应用的path记录到主应用地址栏的query参数上,当刷新浏览器初始化iframe时,从地址栏读到子应用的path并使用iframe的history.replaceState进行同步。
简单理解就是:将子应用路径记录在地址栏参数中。
此[issue]()至今无法在框架层面得到解决,属于iframe的原生限制。手动的解决方案:
用于js沙箱的iframe是隐藏在主应用的body下面的,相当于是常驻内存,这可能会带来额外的内存开销。
京东的大前端团队出品。
样式隔离方案与qiankun的实验方案类似,也是在运行时给子应用中所有的样式规则增加一个特殊标识来限定css作用范围。
子应用路由同步方案与wujie类似,也是通过劫持路由跳转方法,同步记录到url的query中,刷新时读取并恢复。
组件化的使用方式与wujie方案类似,这也是micro-app主打的宣传点。
最有意思的是它的沙箱方案,居然内置了两种沙箱:
开发者可以根据自身的实际情况自由选择。
整体感觉micro-app是一种偏“现实主义”的框架,它的特点就是取各家所长,最终成为了功能最丰富的微前端框架。
静态资源补全是基于父应用的,而非子应用这需要开发者自己手动解决。
主要从接入成本、功能稳定性、长期维护性三方面来衡量:
看你的团队最看重哪一点,针对性去选择就好了,没有十全十美微前端框架,只有适合自己的。