ShadowDom
2024年03月21日
一、ShadowDom
ShadowDom
隔离: 我们可以将每个子应用包裹到一个 Shadow DOM
中,保证其运行时的样式的绝对隔离。
1.1 特点
Shadow DOM
中的全局弹窗: 由于子应用的样式作用域仅在 shadow
元素下,那么一旦子应用中出现运行时越界跑到外面构建 DOM
的场景,必定会导致构建出来的 DOM
无法应用子应用的样式的情况。比如 sub-app
里调用了 antd modal
组件,由于 modal
是动态挂载到 document.body
的,而由于 Shadow DOM
的特性 antd
的样式只会在 shadow
这个作用域下生效,结果就是弹出框无法应用到 antd
的样式。解决的办法是把 antd
样式上浮一层,丢到主文档里,但这么做意味着子应用的样式直接泄露到主文档了
1.2 源码
/**
* 做了两件事
* 1、将 appContent 由字符串模版转换成 html dom 元素
* 2、如果需要开启严格样式隔离,则将 appContent 的子元素即微应用的入口模版用 shadow dom 包裹起来,达到样式严格隔离的目的
* @param appContent = `<div id="__qiankun_microapp_wrapper_for_${appInstanceId}__" data-name="${appName}">${template}</div>`
* @param strictStyleIsolation 是否开启严格样式隔离
*/
function createElement(appContent: string, strictStyleIsolation: boolean): HTMLElement {
// 创建一个 div 元素
const containerElement = document.createElement('div');
// 将字符串模版 appContent 设置为 div 的子与阿苏
containerElement.innerHTML = appContent;
// appContent always wrapped with a singular div,appContent 由模版字符串变成了 DOM 元素
const appElement = containerElement.firstChild as HTMLElement;
// 如果开启了严格的样式隔离,则将 appContent 的子元素(微应用的入口模版)用 shadow dom 包裹,以达到微应用之间样式严格隔离的目的
if (strictStyleIsolation) {
if (!supportShadowDOM) {
console.warn(
'[qiankun]: As current browser not support shadow dom, your strictStyleIsolation configuration will be ignored!',
);
} else {
const { innerHTML } = appElement;
appElement.innerHTML = '';
let shadow: ShadowRoot;
if (appElement.attachShadow) {
shadow = appElement.attachShadow({ mode: 'open' });
} else {
// createShadowRoot was proposed in initial spec, which has then been deprecated
shadow = (appElement as any).createShadowRoot();
}
shadow.innerHTML = innerHTML;
}
}
return appElement;
}
二、Css Module
社区通常的实践是通过约定 css
前缀的方式来避免样式冲突,即各个子应用使用特定的前缀来命名 class
,或者直接基于 css module
方案写样式。对于一个全新的项目,这样当然是可行,但是通常微前端架构更多的目标是解决 存量/遗产 应用的接入问题。很显然遗产应用通常是很难有动力做大幅改造的。
最主要的是,约定的方式有一个无法解决的问题,假如子应用中使用了三方的组件库,三方库在写入了大量的全局样式的同时又不支持定制化前缀?比如 a
应用引入了 antd 2.x
,而 b
应用引入了 antd 3.x
,两个版本的 antd
都写入了全局的 .menu class
,但又彼此不兼容怎么办?