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) // '北京'
相关推荐
zqx_716 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己33 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
feng_xiaoshi1 小时前
【云原生】云原生架构的反模式
云原生·架构
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发