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) // '北京'
相关推荐
FreeCultureBoy35 分钟前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom1 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom1 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom1 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom1 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom1 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom2 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试
LaoZhangAI3 小时前
2025最全GPT-4o图像生成API指南:官方接口配置+15个实用提示词【保姆级教程】
前端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫
沉登c3 小时前
第 3 章 事务处理
架构