首页 > 其他分享 >qiankun(乾坤)解决父子应用样式的影响和策略

qiankun(乾坤)解决父子应用样式的影响和策略

时间:2024-10-08 15:22:11浏览次数:9  
标签:iframe 样式 qiankun href link 应用 乾坤

前言:

qiankun 官网
qiankun(乾坤)微应用框架。可以让很多应用集成到一个项目里来。但集成时样式隔离是个很大的问题(坑)。官网也给出了一些解决方案。

虽然无界 完美的解决了样式隔离的问题(它的底层使用 iframe实现的),同样它也有其他缺点:
无界官网

qiankun也给了为什么不用 iframe的回答:
Why Not Iframe

为什么不用 iframe,这几乎是所有微前端方案第一个会被 challenge 的问题。但是大部分微前端方案又不约而同放弃了 iframe 方案,自然是有原因的,并不是为了 "炫技" 或者刻意追求 "特立独行"。

如果不考虑体验问题,iframe 几乎是最完美的微前端解决方案了。

iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。

1. url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
2. UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
3. 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
4. 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

其中有的问题比较好解决(问题1),有的问题我们可以睁一只眼闭一只眼(问题4),但有的问题我们则很难解决(问题3)甚至无法解决(问题2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。

我们最后选了qiankun。

比如:strictStyleIsolation: true
截图
这个是实验性得api
qiankun start strictStyleIsolation

再比如 ui库样式影响 比如 antd(主应用和子应用都用了这个库):
截图

qiankun 如何确保主应用跟微应用之间的样式隔离

提供的方案,并不能完全解决样式冲突问题,因为 有的ui库并美有提供统一的加前缀功能。

策略:

1.子应用之间不用担心样式影响(乾坤给解决了)。
2.主子应用尽量别用相同的类名。
可以用less、scss 或者 styles modules(样式模块化)

上面的两种可以尽量做到互相不影响样式。但如果 都用了同一个 ui库可能就不适用了。如果 ui库提供了 加前缀的api那还好说(比如:antd)。没提供那就没太好的方法(比如 ag-grid 从子应用切换到主应用,主的ag-grid样式被影响了)。

我审查元素发现,虽然 切换了应用但是,子应用挂载的容器并没有移除,所以想到了动态移除link标签的方法。
在这里插入图片描述

ag-grid我们是用了当切换应用时,动态的移除掉 对应的 style标签,并且把对应的链接存起来,sessionStorage实现。切换到其他应用就移除掉 style。切换回来时动态追加上。
在这里插入图片描述

我们的代码如下仅供参考,给个另类的解决方案:
路由切换时需要调用此方法。
我们子应用都是 sub-开头

//处理 特殊 css 样式  问题, 例如 ag-grid
export const handlingCss = () => {
  //如果是基座  css样式管理
  if (!window.location?.hash.includes('sub-')) {
    // 获取所有 link 元素
    let links = document.getElementsByTagName('link');
    // 创建一个数组,用于存储 sessionStorage 的 值  (pd 系统的 )
    var hrefValues = JSON.parse(sessionStorage.getItem('pdCss'))?.length > 0 ? JSON.parse(sessionStorage.getItem('pdCss')) : [];
    // 循环遍历每个 link 元素
    for (let i = 0; i < links.length; i++) {
      // 检查 href 属性是否包含指定部分
      if (links[i].getAttribute('href').includes(pdDomain[REACT_APP_ENV]) && links[i].getAttribute('type') === "text/css") {
        hrefValues.push(links[i].getAttribute('href'));
        // 删除符合条件的 link 元素
        if (links[i]?.parentNode && links[i]?.parentNode?.contains(links[i])) {
          links[i]?.parentNode?.removeChild(links[i]);
        }
      }
    }
    var uniqueCssLinks = [];
    var cssLinkSet = new Set();

    hrefValues?.forEach(function (link) {
      if (!cssLinkSet.has(link)) {
        cssLinkSet.add(link);
        uniqueCssLinks.push(link);
      }
    });
    // 将数组转换为字符串并存储在 sessionStorage 中
    sessionStorage.setItem('pdCss', JSON.stringify(uniqueCssLinks));
    // 获取所有 <style> 元素
    const styleElements = document.querySelectorAll('style');

    let styleCrm = null
    // 遍历每个 <style> 元素
    styleElements?.forEach((styleElement, index) => {
      // 获取 <style> 元素的文本内容
      const styleContent = styleElement.textContent;

      // 使用正则表达式提取注释代码
      const comments = styleContent.match(/\/\*.*?\*\//g);

      // 打印提取到的注释代码
      comments?.forEach((item) => {
        // 检查 href 属性是否包含指定部分
        if (item.includes(crmDomain[REACT_APP_ENV])) {
          styleCrm = item.replace('/*', '').replace('*/', '');
          // 删除符合条件的 style 元素
          // 在包含指定注释的情况下移除对应的 <style> 元素
          if (styleElement?.parentNode && styleElement?.parentNode?.contains(styleElement)) {
            styleElement?.parentNode?.removeChild(styleElement);
          }
        }
      })
    });
    if (styleCrm) {
      sessionStorage.setItem('crmStyle', styleCrm.trim());
    }
  }else{
    if(window.location?.hash.includes('sub-pdProduct')){
      
      // 从 sessionStorage 中获取存储的 href 值数组
      let storedHrefs = JSON.parse(sessionStorage.getItem('pdCss'));

      // 获取现有的 link 元素
      const existingLinks = [...document.head.getElementsByTagName('link')];
      // 根据获取到的 href 值数组动态创建 link 元素并添加到 head 中
      storedHrefs?.forEach(function (href) {
        // 检查是否已经存在相同 href 的 link 元素
        const alreadyExists = existingLinks.some(link => link.href === href);
        // 如果不存在,则创建并添加新的 link 元素
        if (!alreadyExists) {
          let link = document.createElement('link');
          link.rel = 'stylesheet';
          link.type = 'text/css';
          link.href = href;
          document.head.appendChild(link);
        }

      });
    }
  }
};

之后我又发现 子应用挂载的容器不一样也能解决这个问题(可以尝试一下):
之前是所有子应用挂载到一个容器里#qiankun-imp-wrap,后面 发现挂载不同容器 也能避免 样式冲突。需要注意的是 需要在入口文件里加上两个div id分别为qiankun-imp-wrap、qiankun-imp-wrap1

const childApp = [{
    name: 'sub-pdProduct',
    entry: xxxx + "?version=" + localStorage.getItem("subPdVersion"),
    container: '#qiankun-imp-wrap1', //之前是#qiankun-imp-wrap 
    activeRule: '/#/sub-pdProduct',
  },
  {
    name: 'sub-multi-tabs-admin',
    entry:xxx + "?version=" + localStorage.getItem("subPbVersion"),
    container: '#qiankun-imp-wrap',
    activeRule: '/#/sub-multi-tabs-admin',
  }]
  registerMicroApps(childApp);

建议的解决方案(终极):

我们是这样的,一开始搭建项目,并没有说,要集成很多应用。我们主应用都快开发完了,后面说想集成其他应用,类似一个工作台,用同一套登录,相同的菜单栏。后面 调研 想用qiankun,这时主应用的代码逻辑已经在了。并且项目着急,所以就直接在主应用里写了接入了很多应用。主应用用了antd,子应用也用了,要命的是版本还不一致,并且我们用自己的样式也覆盖了 antd的一些样式。

我们后期是准备这样的 主应用就只是用来接入子应用的,把现在主应用里的代码和页面 拿出去当成子应用。也就是说 主应用就用来当容器,里面可以有少量的逻辑。

主应用就相当于一个纯净的容器只在入口文件 接入子应用,处理一些通用逻辑。(比如:登录逻辑,路由逻辑)。

这样就会避免主子应用有样式冲突。因为qinkun很好的解决了子应用之间的样式隔离。

子应用更新,但访问主应用并没有及时更新。可解看:
qainkun 子应用更新,但是访问主应用时显示的还是旧的内容

标签:iframe,样式,qiankun,href,link,应用,乾坤
From: https://blog.csdn.net/weixin_44058725/article/details/142755486

相关文章

  • 简单实用的Pbootcms页码显示样式代码
    {pboot:if({page:rows}>0)}<divclass="pagebar"><divclass="pagination"><aclass="page-itempage-linkhidden-sm"href="{page:index}"title="首页">首页</a><acl......
  • PbootCMS实现数字条分页样式效果
    在PbootCMS中实现数字条分页效果非常简单,并且可以通过CSS自定义样式。下面详细介绍如何使用PbootCMS的分页组件,并通过CSS修改数字条的外观。1.添加分页代码在需要分页的模板文件中添加以下代码:<!--分页-->{pboot:if({page:rows}>0)}<divclass="pagebar"><divc......
  • 如何修改PbootCMS默认面包屑导航样式及自定义设置方法
    在PbootCMS中,面包屑导航是一个重要的导航元素,帮助用户快速了解当前页面的位置和路径。为了满足不同的设计需求,可以通过自定义参数和修改样式来调整面包屑导航。下面详细介绍如何进行这些自定义操作。PbootCMS面包屑导航调用方式html {pboot:position}自定义参数常用参......
  • PbootCMS网站后台登录页面样式怎么修改
    在PbootCMS中,如果你需要对后台的样式进行修改,通常需要找到相应的文件并进行编辑。下面是详细的步骤和注意事项:后台样式文件位置后台样式的文件通常位于以下路径:plaintext 根目录/apps/admin/view/default/index.html修改步骤备份文件:在修改任何文件之前,请务必先备......
  • Bootstarp基本样式
    Bootstarp基本样式1表单form-group->该表单框距离下面表单框多了一些距离form-control->将所在表单框拉长参考链接:全局CSS样式·Bootstrapv3中文文档|Bootstrap中文网(bootcss.com)<form><divclass="form-group"><labelfor="exampleInpu......
  • cnBlogs的自定义样式
    存个备份.navbara:link,.navbara:active,.navbara:visited{color:#666;text-decoration:none}.navbara:hover{color:#666;text-decoration:underline}.navbar>nav.navbar-avatar{border-radius:50%}.post-item.avatar{......
  • PbootCMS模板安装后首页打开样式错乱的解决方法
    在使用PBootCMS模板安装后,如果发现首页打开时样式错乱,通常是因为样式表(CSS文件)没有正确加载。以下是一些常见的解决方法:解决方法检查域名设置确保在后台正确设置了站点的域名。这一步非常重要,因为很多静态资源的路径依赖于域名。步骤:登录PBootCMS后台。进入“站点信......
  • 解决 PBootCMS 模板安装后首页样式错乱的问题
    <!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>示例页面</title><!--确保base标签设置正确--><basehref="http://www.example.com/"><!--引......
  • 第四章 CSS样式基础
    目录4.1CSS概述4.1.1.CSS的基本概念4.1.2传统HTML的缺点4.1.2.1.维护困难4.1.2.2.标记不足4.1.2.3.网页过“胖”4.1.2.4.定位困难4.1.3.CSS的特点和优势4.1.3.1.表现和内容分离4.1.3.2.增强了网页的表现力4.1.3.3.使整个网站显示风格趋于统一4.1.4.CSS的编写规则......
  • 第四章 CSS样式基础
    4.1CSS概述4.1.1CSS的基本概念CSS中文释义为“层叠样式表”,它是以HTML为基础,设置网页的外观显示样式,如字体、颜色、背景的控制及整体的布局等,还可以针对不同的浏览器设置不同的样式4.1.2传统HTML的缺点1.维护困难:为了修改某个特殊标记的格式,需要花费很多时间,尤其是对......