无界微前端

news/2024/11/15 17:46:54/文章来源:https://www.cnblogs.com/chinasoft/p/18202621

https://zhuanlan.zhihu.com/p/657544258

 

 

背景

什么是微前端

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略

玉伯:今天看各 BU 的业务问题,微前端的前提,还是得有主体应用,然后才有微组件或微应用,解决的是可控体系下的前端协同开发问题(含空间分离带来的协作和时间延续带来的升级维护)

应用场景

大多是 toB 应用或者中后台系统

  1. 应用聚合:聚合多个应用,提供统一入口。在保障用户体验的同时,赋能业务。
  2. 新老系统并存上线:在老系统中新增迭代时,以微前端的形式单独创建一个子应用,无关技术栈。
  3. 多团队协同问题:摆脱技术栈的约束及部署冲突,高效协作

核心:无关技术栈、独立运行/开发/部署、渐近升级

通过引入微应用,除了解决了工程上的问题,同时让产品拥有可自由重组的能力,让产品价值最大化

为什么还要造新的微前端框架?

目前已经有不少较成熟的微前端方案,像 qiankun、micro-app 等方案,为什么还要造新的微前端框架?

qiankun 不足:

  • 适配成本比较高,工程化、生命周期、静态资源路径、路由等都要做一系列的适配工作;
  • css 沙箱采用严格隔离会有各种问题,js 沙箱在某些场景下执行性能下降严重;
  • 无法同时激活多个子应用,也不支持子应用保活;
  • 无法支持 vite 等 esm 脚本运行;

micro app 不足:

  • css 沙箱依然无法绝对的隔离
  • 支持 vite 运行,但必须使用 plugin 改造子应用,且 js 代码没办法做沙箱隔离;
  • 对于不支持 webcompnent 的浏览器没有做降级处理;

结论:
qiankun 方案对 single-spa 微前端方案做了较大的提升同时也遗留下来了不少问题长时间没有解决;
micro app 方案对 qiankun 方案做了较多提升但基于 qiankun 的沙箱也相应会继承其存在的问题;

目前的微前端方案在用户的核心诉求上都没有很好的满足,有很大的优化提升空间。

无界方案

无界利用 iframe 和 webcomponent 来搭建天然的 js 沙箱和 css 沙箱

iframe 特点

优点

  1. 非常简单,使用没有任何心智负担
  2. web应用隔离的非常完美,无论是 js、css、dom 都完全隔离开来

缺点

  1. 路由状态丢失,刷新一下,iframe 的 url 状态就丢失了
  2. dom 割裂严重,弹窗只能在 iframe 内部展示,无法覆盖全局
  3. web 应用之间通信非常困难
  4. 每次打开白屏时间太长,对于 SPA 应用来说无法接受

接下来看一下无界微前端框架是如何通过继承 iframe 的优点,一步一步解决 iframe 缺点的。

设计原理

 

 

渲染子应用步骤:

  1. 创建和主应用同源的 iframe,路径携带了子路由的路由信息
    同源是为了方便应用间的通信,子应用需要能支持跨域;iframe 实例化完成后需立即中断加载 html,防止进入主应用的路由逻辑污染子应用
  2. 解析子应用的入口 html
    识别出 html 部分,分离 style 和 js;处理 css 重新注入html ;创建 webComponent 并挂载 html
  3. 创建 script 标签,并插入到 iframe 的 head 中
  4. 在 iframe 中拦截 document 对象,统一将 dom 指向 shadowRoot
    这样弹窗或者冒泡组件就可以正常覆盖主应用

接下来的三步分别解决iframe的三个缺点:

✅ 通信非常困难的问题:iframe 和主应用是同域的,天然的共享内存通信,而且无界提供了一个去中心化的事件机制

✅ dom 割裂严重的问题:主应用提供一个容器给到 shadowRoot 插拔,shadowRoot 内部的弹窗也就可以覆盖主应用

✅ 路由状态丢失的问题:浏览器的前进后退可以天然的作用到 iframe 上,此时监听 iframe 的路由变化并同步到主应用,如果刷新浏览器,就可以从 url 读回保存的路由

将这套机制封装进无界框架:

 

 

我们可以发现:
✅ 首次白屏的问题:wujie 实例可以提前实例化,包括 shadowRoot、iframe 的创建、js 的执行,以此来加快子应用首次打开的时间
✅ 切换白屏的问题:一旦 wujie 实例可以缓存下来,子应用的切换成本变的极低,如果采用保活模式,那么相当于 shadowRoot 的插拔

会带来一定对内存开销:未激活子应用的 shadowRoot 和 iframe 内存常驻,保活模式每个页面都需要独占一个 wujie 实例

应用加载机制和 js 沙箱机制

将子应用的 js 注入主应用同域的 iframe 中运行,iframe 是一个原生的 window 沙箱,内部有完整的 history 和 location 接口,子应用实例 instance 运行在 iframe 中,路由也彻底和主应用解耦。

/*** iframe插入脚本* @param scriptResult script请求结果* @param iframeWindow* @param rawElement 原始的脚本*/
export function insertScriptToIframe(scriptResult: ScriptObject | ScriptObjectLoader,iframeWindow: Window,rawElement ? : HTMLScriptElement) {const { src, module, content, crossorigin, crossoriginType, async, attrs, callback, onload } =scriptResult as ScriptObjectLoader;// ...if (!iframeWindow.__WUJIE.degrade && !module) {code = `(function(window, self, global, location) {${code}}).bind(window.__WUJIE.proxy)(window.__WUJIE.proxy,window.__WUJIE.proxy,window.__WUJIE.proxy,window.__WUJIE.proxyLocation,);`;}// ...
}

iframe 连接机制和 css 沙箱机制

css 沙箱机制: 采用 webcomponent 创建一个 wujie 自定义元素来实现页面的样式隔离,将子应用的完整结构渲染在内部
iframe 连接机制: 子应用的实例 instance 在 iframe 内运行,dom 在主应用容器下的 webcomponent 内,通过代理 iframe 的 document 到 webcomponent,可以实现两者的互联;将 document 的查询类接口全部代理到 webcomponent,这样 instance 和 webcomponent 就精准的链接起来
shadowRoot的插拔: 当子应用发生切换,iframe保留下来,子应用的容器可能销毁,但 webcomponent依然可以选择保留,这样等应用切换回来将 webcomponent 再挂载回容器上,子应用可以获得类似 vue 的 keep-alive 的能力

路由同步机制

在 iframe 内部进行 history.pushState,浏览器会自动的在 joint session history 中添加 iframe 的 session-history,浏览器的前进、后退在不做任何处理的情况就可以直接作用于子应用(所有 iframe 页面会共享 top 页面的 history)

劫持 iframe 的 history.pushState 和 history.replaceState,就可以将子应用的 url 同步到主应用的 query 参数上,当刷新浏览器初始化 iframe 时,读回子应用的 url 并使用 iframe 的 history.replaceState 进行同步

/*** 对iframe的history的pushState和replaceState进行修改* 将从location劫持后的数据修改回来,防止跨域错误* 同步路由到主应用* @param iframeWindow* @param appHostPath 子应用的 host path* @param mainHostPath 主应用的 host path*/
function patchIframeHistory(iframeWindow: Window, appHostPath: string, mainHostPath: string): void {const history = iframeWindow.history;const rawHistoryPushState = history.pushState;const rawHistoryReplaceState = history.replaceState;history.pushState = function(data: any, title: string, url ? : string): void {const baseUrl =mainHostPath + iframeWindow.location.pathname + iframeWindow.location.search + iframeWindow.location.hash;const mainUrl = getAbsolutePath(url?.replace(appHostPath, ""), baseUrl);const ignoreFlag = url === undefined;rawHistoryPushState.call(history, data, title, ignoreFlag ? undefined : mainUrl);if (ignoreFlag) return;updateBase(iframeWindow, appHostPath, mainHostPath);syncUrlToWindow(iframeWindow);};history.replaceState = function(data: any, title: string, url ? : string): void {const baseUrl =mainHostPath + iframeWindow.location.pathname + iframeWindow.location.search + iframeWindow.location.hash;const mainUrl = getAbsolutePath(url?.replace(appHostPath, ""), baseUrl);const ignoreFlag = url === undefined;rawHistoryReplaceState.call(history, data, title, ignoreFlag ? undefined : mainUrl);if (ignoreFlag) return;updateBase(iframeWindow, appHostPath, mainHostPath);syncUrlToWindow(iframeWindow);};
}

我们还可以在页面上同时激活多个子应用,由于 iframe 和主应用处于同一个top-level browsing context,因此浏览器前进、后退都可以作用到到子应用:

 

通信机制

props 注入机制
子应用通过 $wujie.props 可以轻松拿到主应用注入的数据

window.parent 通信机制
子应用 iframe 沙箱和主应用同源,子应用可以直接通过 window.parent 和主应用通信

去中心化的通信机制
无界提供了 EventBus 实例,注入到主应用和子应用,所有的应用可以去中心化的进行通信

插件系统

无界提供强大的插件系统,方便用户在运行时去修改子应用代码从而避免将适配代码硬编码到仓库中。

 

  • html-loader 可以对子应用 template 进行处理
  • js-excludes 和 css-excludes 可以排除子应用特定的 js 和 css 加载
  • js-before-loaders、js-loader、js-after-loaders 可以方便的对子应用 js 进行自定义
  • css-before-loaders、css-loader、css-after-loaders 可以方便的对子应用 css 进行自定义

生命周期

无界提供完善的生命周期钩子供主应用调用:

beforeLoad:子应用开始加载静态资源前触发
beforeMount:子应用渲染前触发 (生命周期改造专用)
afterMount:子应用渲染后触发(生命周期改造专用)
beforeUnmount:子应用卸载前触发(生命周期改造专用)
afterUnmount:子应用卸载后触发(生命周期改造专用)
activated:子应用进入后触发(保活模式专用)
deactivated:子应用离开后触发(保活模式专用)

vite 框架支持

无界子应用运行在 iframe 中原生支持 esm 的脚本,而且不用担心子应用运行的上下文问题

兼容 IE9

由于无界采用了 webcomponent + shadowdom + proxy 的方案,在某些低版本浏览器上无法运行时,无界微前端会自动降级。

降级方案采用:

  • webcomponent + shadowdom ⇒ iframe(dom-iframe)
  • proxy + Object.defineproperty ⇒ Object.defineproperty
  • 子应用运行的方式是 dom-iframe + js-iframe + Object.defineproperty,IE9+ 都可以兼容

自动降级后无界依然可以保证子应用的 css 和 js 原生隔离,但是由于 dom-iframe 的限制,弹窗将只能在子应用内部打开
由于无法使用proxy,无法劫持子应用的location,导致访问 window.location.host 的时候拿到的是主应用的 host,子应用可以从 $wujie.location 中拿到子应用正确的 host

设计上的不足

  1. 引入了一个空白的 iframe 带来额外内存开销:初始化子应用的时候需要准备一个空白的 iframe 沙箱,然后将子应用的js注入进去执行
  2. 还有一个不是很完美的点,防止进入主应用的路由逻辑污染子应用停止时机:iframe 沙箱的 src 设置了主应用的 host,初始化 iframe 的时候需要等待 iframe 的 location.orign 从 'about:blank' 初始化为主应用的 host(这样子应用路由就可以正常的调用 window.history.push 等 api),这个等待是采用setTimeout 轮询来查看 location.orign 是否已经 ready,在等待过程中iframe有可能已经开始加载主应用的 js 文件从而导致污染,这个问题框架做了一定处理但是目前还没有特别好的办法
/*** 防止运行主应用的js代码,给子应用带来很多副作用*/
// TODO: 更加准确抓取停止时机
function stopIframeLoading(iframeWindow: Window) {const oldDoc = iframeWindow.document;return new Promise<void>((resolve) => {function loop() {setTimeout(() => {let newDoc = null;try {newDoc = iframeWindow.document;} catch (err) {newDoc = null;}// wait for document readyif (!newDoc || newDoc == oldDoc) {loop();} else {iframeWindow.stop ? iframeWindow.stop() : iframeWindow.document.execCommand("Stop");resolve();}}, 1);}loop();});
}

技术选型建议

对比qiankunmicro-appwujie
首个版本 4年 (2019-08-01) 2年 (2021-07-09) 1年 (2022-07-05)
最近更新 v2.10.8 (2023-05-17) v1.0.0-beta.4 (2023-04-27) 1.0.16 (2023-05-17)
包体积 94kb 30kb 11kb
接入成本 较低
ie ×
数据通信机制 props addDataListener props、window、eventBus
js沙箱
样式隔离
元素隔离 ×
静态资源地址补全 ×
预加载
keep-alive ×
应用共享同一个资源
应用嵌套
插件系统 ×
子应用不改造接入 ×
  • 考虑系统需要兼容 ie 浏览器场景
    wujie > qiankun
  • 接入便捷度考虑
    wujie > micro-app > qiankun
  • 框架稳定性 (框架成熟度)
    qiankun > micro-app > wujie

展望

更低的接入成本: 目前的微前端框架使用中对于子应用的接入还是存在一些改造成本,相对于注册机制来说组件式的引用方式更灵活改造成本也更低

标准化的沙箱能力: 可以看到现阶段大部分的微前端框架都有自己的沙箱处理机制且各不相同,在这类问题上应该可以期待 W3C 提供一些标准化的沙箱能力,来处理这些同质化问题

更加灵活: 目前部分微前端方案中还是存在一些技术约束,如 EMP 依赖 webpack、乾坤也依赖 umd 的打包方式、对 vite 的支持。后续应该会更灵活更少约束

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/709371.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[MySQL]存储过程

本篇文章阐述的原则是“以吾之理解,着重之阐述”,因此没有那么细致。 如果文中阐述不全或不对的,多多交流。【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://www.cnblogs.com/cnb-yuchen/p/18032044 出自【进步*于辰的博客】存储过程的细节很多…

esp8266-01 使用介绍

一、直接使用接线esp8266USB转TTl说明TX RX ------RX TX ------EN 3.3V AT命令 需要拉高 ------3V3 3.3V ------IO0 不接 IO0接地进入烧录模式GND GND ------二、固件烧写 前提说明一般是模块固件损坏或者买回来里面可能被别人刷过固件需要擦除或者增加固件才用,在这里结合我…

[转帖]Linux内存管理基本概念

最近在学习Linux系统的内存管理,小白一枚,零散从网上收集的一些笔记如下: /proc目录提供了很多工具给我们查看当前内存情况 1. /proc/meminfo是什么 $cat /proc/meminfoMemTotal: 2052440 kB //总内存MemFree: 50004 kB //空闲内存Buffers: 1…

机器学习中的正则化技术——Python实现

在机器学习中,我们非常关心模型的预测能力,即模型在新数据上的表现,而不希望过拟合现象的的发生,我们通常使用正则化(regularization)技术来防止过拟合情况。正则化是机器学习中通过显式的控制模型复杂度来避免模型过拟合、确保泛化能力的一种有效方式。如果将模型原始的…

ConfigurationClassPostProcessor类,@Configuration注解的底层实现

概览 由前文可知,ConfigurationClassPostProcessor是作为Spring中的内置类被添加到容器中,【源码学习】Spring启动流程ConfigurationClassPostProcessor不仅实现了BeanFactoryPostProcessor(BFPP)并且实现了BeanDefinitionRegistryPostProcessor,具有比一般BFPP更高的初始…

随机二次元图片API第三弹

本来我都把第二弹置顶上来了,没打算在发第三弹的,然后想着想着又憋出来这么多话,想想不发不就白浪费我那么多脑细胞了。Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 随机二次元图…

记一次解决OTA死机重启bug,如何分析与解决措施?!

背景: 平台:stm32mp151平台 什么是OTA? 说起OTA我们应该都不陌生,它是一种可以为设备无损失升级系统的方式,能将新功能远程部署到产品上。 我们不仅可以通过网络下载OTA升级包,也可以通过下载OTA升级包到SD卡或U盘后再对设备升级。 OTA下载方式:短信方式 PUSH方式 网络定…

插件助手

Fitten Codevscode安装插件先注册登录智能补全问答生成代码选择代码,编辑代码Github Copilot Kite TabNine

Google Cloud Next ’24 Recap 开启 AI 新篇章,Cloud Ace 独立解决方案助力企业降本增效

北京时间 2024年4月26日,Cloud Ace 云一 受邀参与 Google Cloud Next’24 Recap 在深圳的线下活动,并设置展位。本次活动主要聚焦于 Next’24(Las Vegas)成果展示,给中国客户和开发者深入解读 Google Cloud Next ’24 大会上 Gemini、Vertex AI、BigQuery 等产品服务的重要…

感谢西部数码大佬寄的国庆礼物

事情是这样的Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 感谢西部数码大佬寄的国庆礼物 日期:2019-10-18 阿珏 谈天说地 浏览:1499次 评论:6条事情是这样的然后就这样最后就得…