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) // '北京'
相关推荐
天空属于哈夫克31 天前
企业微信二次开发:精准实现关键词自动回复
架构·企业微信
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
晨米酱1 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
这个DBA有点耶1 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
码流子1 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构