微前端
微前端优点:
- 不限技术栈:主框架不限制接入应用的技术栈,子应用可自主选择技术栈。
- 可独立开发部署:各个团队之间仓库独立,单独部署,互不依赖。
- 独立运行时:每个子应用之间状态隔离,运行时状态不共享。
- 增量升级:当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性。
iframe的问题:
- URL 不同步。浏览器刷新
iframe url
状态丢失、后退前进按钮无法使用。 - UI 不同步,DOM 结构不共享。如无法显示整页弹窗。
- 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
- 加载慢,每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
实现方式:构建时组合
模块联邦 emp
虽然 EMP 较好的解决了子应用动态更新的问题,但从实际微前端使用场景来说还需要考虑子应用间的相互影响,需要处理 JS 沙箱、CSS 隔离等问题。EMP 的方案更适用于微组件而非微应用的场景。
实现方式:运行时组合
独立构建的子应用, 由主应用在运行时加载
qiankun
基座 Entry 通过 JS 加载子应用是最灵活的方法,也是目前最常采用的方法。 每个子应用按约定暴露出相应的生命周期钩子,并且在加载后将其绑定到 window 对象下给主应用访问。 然后主应用程序确定渲染哪个子应用,调用相关渲染函数传入渲染节点。
基于 single-spa
实现路由与子应用的绑定关系根据路由加载相应应用。子应用将自己的信息注册到主应用中,包括入口文件地址、对应生效路由及命名空间等信息。同时子应用需暴露几个关键的生命周期钩子 bootstrap、mount、unmount
,以供主应用在适当的时机调用。
提供两套 JS 沙箱方案:
- 在不支持 proxy 的环境下用快照的方式在加载子应用前记录全局状态并在卸载时还原记录的状态。
- 在支持 proxy 时,劫持对 window 的操作并创建一个 fakeWindow 对象,赋值操作都发生在 fakeWindow 对象下,取值时按照 fakeWindow -> window 的顺序依次查找。
无界
基于 iframe
实现 js 沙箱,通过 WebComponent
处理 css 隔离 大致实现方式为:运行时动态加载子应用资源(加载方式在下文技术细节中会详细说明),在主应用中创建一个 shadowdom
节点和一个 iframe
。将 js 注入 iframe
内运行,将 dom、css 放到 shadowdom
节点下。同时劫持 js 中的 dom 操作并指向 shadowdom
。
在路由状态方面 通过劫持iframe
的history.pushState和history.replaceState将子应用的url同步到主应用的query参数上,当刷新浏览器初始化iframe
时,读回子应用的url并使用iframe
的history.replaceState进行同步
👆 无界 携程 怎么解决这些问题
micro-app
micro-app借鉴了 WebComponent
的思想,通过 CustomElement 结合自定义的 ShadowDom
,将微前端封装成一个类 WebComponent
组件,从而实现微前端的组件化渲染。并且由于自定义 ShadowDom
的隔离特性,micro-app不需要像single-spa和qiankun一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改 webpack 配置,接入成本较低。
EMP 依赖 webpack、乾坤也依赖 umd 的打包方式
CSS 隔离
Dynamic Stylesheet
在应用切出/卸载后,同时卸载掉其样式表即可,原理是浏览器会对所有的样式表的插入、移除做整个 CSSOM 的重构,从而达到 插入、卸载 样式的目的
这样即能保证,单应用场景下在一个时间点里,只有一个应用的样式表是生效的
👆 也就是常见的前端应用生产环境打包 CSS 的方式, 分离出单独的 CSS 文件, 自然而然的实现了切换子应用 CSS 互不干扰
编译改造
提供一个 postcss 插件,在每个应用构建的时候给所有的样式都加上应用前缀包括应用公共库的 CSS。
域隔离
主应用为每个 css
规则添加特定的前缀来起到隔离的作用,例如微应用中的样式是 p{color:#000}
, 处理后为 .app1 p {color:#000}
创建一个临时的 style
节点。 通过 style
的 sheet
属性来获取一条条规则。 然后调用 ruleStyle
进行转化,转化是通过正则进行匹配然后替换。 最后把转化后的内容替换到原有的 style
节点中。