single-spa模式微前端的副作用及解决方案

采用single-spa模式挂载的子应用与基座之间可能会存在css样式和js全局变量覆盖问题。下面将从这两种副作用着手,说下可以采用的哪些解决方案解决这些副作用。

如果您对single-spa还比较陌生,可以按需查看下列文章:

1.初识single-spa

2.single-spa源码

3.single-spa模式微前端副作用(本篇)

一、css样式覆盖问题

针对样式覆盖可以采用以下几种解决方案解决:

  1. shadow DOM隔离

shadow DOM并不是一个新的概念,这是个老概念了,目前h5的一些原生组件就是用shadow DOM写成的。为避免有人不清楚这个概念,先在这里做下shadow DOM概念的普及。

Web components 的一个重要属性是封装------可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。其中,Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上。from MDN

下面介绍下如何将标记结构、样式和行为隐藏

js 复制代码
// 这是一段自定义模板,里面包含了简单的样式和一个button原生组件
<template id="custom-element-button">
   <style>
      .custom-element-button {
          border: none;
          background: green;
          color: white;
      }
   </style>
   <button id="button" class="custom-element-button">点我</button>
</template>
// 这是一段js代码,该代码主要是将上述template模板附加到一个元素上。

class CustomElementButton extends HTMLElement {
   constructor() {
       super();
       const template = document.querySelector('#custom-element-button');
        //添加事件 
       template.content.querySelector('#button').onclick = () => {
           this.onClick();
       }
        // 将shadow DOM 附加到 custom element上的关键
       this.attachShadow({mode: 'open'}).appendChild(template.content);
   }

   onClick() {
       console.log('点我了,shadowdom内部');
   }
}
// 在window上注册一个customElement组件
window.customElements.define('custom-element-button', CustomElementButton);


// 在html中调用
<custom-element-button></custom-element-button>

将html文件在浏览器中打开,就会看到如下图所示按钮,按钮的样式是绿色背景白色文字。点击按钮会在控制台输出点我了,shadowdom内部

此时,如果我们想要在shadow DOM外部修改custom-element-button的样式,直接style标签中修改是不生效的。我们需要借助::part()修改样式。

js 复制代码
<template id="custom-element-button">
   <style>
      .custom-element-button {
          border: none;
          background: green;
          color: white;
      }
   </style>
                        //  注意:在此处添加part
   <button id="button" part="button" class="custom-element-button">点我</button>
</template>

<style>
custom-element-button::part(button) {
   background: red;
   color: green;
}
</style>

下图即为在外部修改样式之后的按钮:

  1. postcss-prefix-selector

借助该loader,可以给子应用的css样式配置一个统一的前缀。

js 复制代码
css: {
  loaderOptions: {
      postcss: {
          plugins: [
              prefixer({
                  prefix: '.micro-slave',
              }),
          ],
      },
  },
},

该配置会将子应用的css样式统一配置为 .micro-slave .xxx

  1. vue项目可以使用scoped属性,减少公共样式,增加私有样式
  2. react项目可以使用style module

第1、2种解决方案不能解决弹窗挂载到body上的样式问题,如果有此类样式冲突,可以尝试定点解决。

二、js 全局变量覆盖问题

针对window上全局变量覆盖问题,可以采用以下沙箱方案解决:

  1. 快照沙箱代理
js 复制代码
//   基于diff 方式隔离
 function iter(obj, callbackFn) {
     for(const prop in obj) {
         if(obj.hasOwnProperty(prop) || prop === 'clearInterval') {
             callbackFn(prop);
         }
     }
 }
 // 快照模式沙箱代理
 class SnapshotSandbox {
     windowSnapshot = {};
     modifyPropsMap = {};
     sandboxRunning = false;

     constructor(name) {
         this.name = name;
         this.proxy = window;

     }

     active() {
         this.windowSnapshot = {};
         iter(window, (prop) => {
             this.windowSnapshot[prop] = window[prop];
         });

         Object.keys(this.modifyPropsMap).forEach(key => {
             window[key] = this.modifyPropsMap[key];
         });

         this.sandboxRunning = true;
     }

     inactive() {
         this.modifyPropsMap = {};
         iter(window, (prop) => {
             if(window[prop] !== this.windowSnapshot[prop]) {
                 this.modifyPropsMap[prop] = window[prop];
                 window[prop] = this.windowSnapshot[prop];
             }
         });

         this.sandboxRunning = false;
     }
 }

 const snap = new SnapshotSandbox();

 window.name = 'snap';

 snap.active();
 window.age = 20;
 console.log(window.age, 'window.age') // 20 
 snap.inactive();
 console.log(window.age, 'window.age') // undefined
 snap.active();
 console.log(window.age, 'window.age') // 20

快照沙箱的缺点是性能比较差,每次都要遍历window上的所有属性;然后不能在多实例情况下使用。

  1. proxy代理沙箱

该沙箱比快照沙箱性能好,并且也能在多实例下使用。

js 复制代码
class ProxySandbox {
   constructor() {
       this.isRunning =false;
       const fakeWindow = Object.create(null);
       
       this.proxyWindow = new Proxy(fakeWindow, {
           set: (target, prop, value) => {
               if(this.isRunning) {
                   target[prop] = value;
                   return true;
               }
           },
           get: (target, prop) => {
               return prop in target ? target[prop]: window[prop]
           }
       })

   }

   active() {
       this.isRunning = true;
   }
   inactive() {
       this.isRunning = false;
   }
}

window.city = '北京'

const p1 = new ProxySandbox()
const p2 = new ProxySandbox()

// 激活
p1.active()
p2.active()

p1.proxyWindow.city = '上海'
p2.proxyWindow.city = '杭州'

console.log(p1.proxyWindow.city) // '上海'
console.log(p2.proxyWindow.city) // '杭州'
console.log(window.city) // 北京

// 失活
p1.inactive()
p2.inactive()

console.log(p1.proxyWindow.city) // '上海'
console.log(p2.proxyWindow.city) // '杭州'
console.log(window.city) // '北京'
相关推荐
用户214965158987519 分钟前
从零搭建uniapp环境-记录
前端
liulilittle22 分钟前
LwIP协议栈MPA多进程架构
服务器·开发语言·网络·c++·架构·lwip·通信
努力写代码的熊大2 小时前
stack、queue与priority_queue的用法解析与模拟实现
java·前端·javascript
im_AMBER2 小时前
React 06
前端·javascript·笔记·学习·react.js·前端框架
特立独行的猫a2 小时前
ESP32使用笔记(基于ESP-IDF):小智AI的ESP32项目架构与启动流程全面解析
人工智能·架构·esp32·小智ai
运维行者_2 小时前
DDI 与 OpManager 集成对企业 IT 架构的全维度优化
运维·网络·数据库·华为·架构·1024程序员节·snmp监控
wyzqhhhh2 小时前
前端常见的设计模式
前端·设计模式
IT_陈寒2 小时前
React 19重磅前瞻:10个性能优化技巧让你少写30%的useEffect代码
前端·人工智能·后端
报错小能手3 小时前
项目——基于C/S架构的预约系统平台(3)
linux·开发语言·笔记·学习·架构·1024程序员节