前言
在qiankun中,提供了2种css样式隔离的配置,在这里先简单介绍一下它们的原理
-
experimentalStyleIsolation
-
原理: 使用动态样式前缀来实现样式隔离。
-
具体实现:
-
为每个微应用的样式添加一个唯一的前缀。
-
动态修改微应用的样式表,将选择器添加前缀。
-
为微应用的根元素添加相应的前缀类名。
-
-
strictStyleIsolation
- 原理:使用 Shadow DOM 来实现严格的样式隔离。
- 具体实现:
- 为每个微应用创建一个 Shadow DOM。
- 将微应用的内容渲染到这个 Shadow DOM 中。
- Shadow DOM 提供了一个独立的 DOM 树和样式作用域。
由于qiankun官方提示将在3.0移除strictStyleIsolation,所以我使用了experimentalStyleIsolation方案
但当qiankun开启experimentalStyleIsolation方式的样式隔离时,会碰到antd组件库全局样式丢失的问题,包括但不限于button、select等组件的样式丢失、失效问题,在各大网站搜索无果,所以自行探索了一下,做一波分享。
本文相关技术栈:
Antd@5.19
qiankun@2.10
antd全局样式丢失问题
现象
在基座应用中 / 独立运行时
问题定位
当我们检查基座应用中的style时,可以发现一个问题:
上图:antd的全局样式被覆盖,下图:qiankun的experimentalStyleIsolation隔离样式(罪魁祸首)
解决方案
在antd5.x后,加入了:where选择器来降低antd全局样式的css权重。
Ant Design 的 CSS-in-JS 默认通过
:where
选择器降低 CSS Selector 优先级,以减少用户升级时额外调整自定义样式的成本,不过:where
语法的兼容性在低版本浏览器比较差 --Ant Design官网 v5.x
所以,我们只需要取消:where选择题,提高antd的css样式权重,就可以将样式恢复正常。但怎么做呢?正好AntD提供了一个官方的API:StyleProvider
import { StyleProvider } from '@ant-design/cssinjs';
// `hashPriority` 默认为 `low`,配置为 `high` 后,
// 会移除 `:where` 选择器封装
export default () => (
<StyleProvider hashPriority="high">
<MyApp />
</StyleProvider>
);
这个API的本意是提供一个5.x版本兼容旧版本浏览器的方法,但实现的原理是将:where选择器移除,用class选择器代替。正好可以满足我们的使用场景,提升antd全局样式的权重。
加上后:
风险:
让antd的全局样式突破experimentalStyleIsolation的隔离,可能会污染基座应用的样式。
(推测场景:基座应用使用antd、微应用使用antd,且它们使用了不一样的主题)
如果你发生了这样的情况,可以尝试使用 antd的 <ConfigProvider prefixCls="custom-">提供一个custom前缀,避免这个情况。
到这里为止,看起来已经解决了样式问题,但当我打开微应用中的Modal组件时,又出现了新的问题:
Antd的Modal组件样式丢失 / 挂载位置错误问题
现象
上图为基座应用中加载微应用,下图为独立运行时
下图为在基座运行时的另一个页面,modal出现在页面的下方
问题定位
当我们检查基座应用中的dom层级时,可以发现一个问题
默认情况下,Ant Design 的 Modal 组件会被渲染到 document.body
。(原理是modal内部实现时使用了React.createPortal ) 。
这意味着 Modal 的 DOM 结构会被移到微应用的 Shadow DOM 或样式隔离容器之外。 (对应strictStyleIsolation / experimentalStyleIsolation 的隔离方案),导致了样式的丢失
解决方案
使用Modal的API - getContainer
<Modal {...props} getContainer={false} />
// 指定 Modal 挂载的节点,但依旧为全屏展示,false 为挂载在当前位置
// 如果一个个写太麻烦,可以封装这样一个组件,后续都使用该组件而不是antd的modal
使用后,modal挂载的位置在样式隔离容器 / shadowDom内部 , modal样式丢失的问题得到解决,因为dom的层级太深,这里不再贴图了。
可能会有人说在Modal中一个个加太麻烦了,为什么不用Antd提供的ConfigProvider
<ConfigProvider
getPopupContainer={node => {
if (node) {
return node.parentNode;
}
return document.body;
}}
>
<App />
</ConfigProvider>
我尝试过,但console出getPopupContainer函数传递的node不是我想要的目标node,且样式问题也仍然存在,了解原理的同学欢迎评论指正