采用single-spa模式挂载的子应用与基座之间可能会存在css样式和js全局变量覆盖问题。下面将从这两种副作用着手,说下可以采用的哪些解决方案解决这些副作用。
如果您对single-spa还比较陌生,可以按需查看下列文章:
3.single-spa模式微前端副作用(本篇)
一、css样式覆盖问题
针对样式覆盖可以采用以下几种解决方案解决:
- 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>
下图即为在外部修改样式之后的按钮:
- postcss-prefix-selector
借助该loader,可以给子应用的css样式配置一个统一的前缀。
js
css: {
loaderOptions: {
postcss: {
plugins: [
prefixer({
prefix: '.micro-slave',
}),
],
},
},
},
该配置会将子应用的css样式统一配置为 .micro-slave .xxx
。
- vue项目可以使用scoped属性,减少公共样式,增加私有样式
- react项目可以使用style module
第1、2种解决方案不能解决弹窗挂载到body上的样式问题,如果有此类样式冲突,可以尝试定点解决。
二、js 全局变量覆盖问题
针对window上全局变量覆盖问题,可以采用以下沙箱方案解决:
- 快照沙箱代理
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上的所有属性;然后不能在多实例情况下使用。
- 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) // '北京'