一、CSS Modules 能做什么
对类选择器 和 ID 选择器基于文件路径 + 类名做 hash,保证组件间类名不冲突。
css
// button.module.scss
.button { color: red; }
编译后:
css
.button_a1b2c3 { color: red; }
JS 侧同步生成映射:
css
const styles = { button: 'button_a1b2c3' }
<button className={styles.button}>Click</button>
编译规则
| 选择器类型 | 是否加 hash | 示例 |
|---|---|---|
类选择器 .button |
✅ | .button_a1b2c3 |
ID 选择器 #header |
✅ | #header_d4e5f6 |
元素选择器 h1 |
❌ 原样输出 | h1 |
通配符 * |
❌ 原样输出 | * |
:root |
❌ 原样输出 | :root |
二、CSS Modules 解决不了的问题
问题一:全局样式是裸的
全局样式的目的本来就是作用于整个页面,用的都是元素选择器,CSS Modules 不处理。
css
// global.scss(不是 .module.scss)
* { box-sizing: border-box; }
body { margin: 0; }
h1 { font-size: 24px; }
如果强行放进 .module.scss,编译后变成无效选择器,完全不生效:
css
*_a1b2c3 { box-sizing: border-box; } /* 不存在的选择器 */
body_x9y8z7 { margin: 0; } /* 不存在的选择器 */
微前端里的影响: 多个子应用的全局样式同时注入同一页面,互相覆盖,谁后加载谁生效。
xml
<head>
<style>/* 子应用 A */ body { margin: 0; } h1 { color: red; }</style>
<style>/* 子应用 B */ body { margin: 10px; } h1 { color: blue; }</style>
<!-- B 覆盖了 A -->
</head>
问题二:第三方库样式是全局的
arduino
import 'antd/dist/antd.css' // 全局注入,CSS Modules 管不到
两个子应用引入了不同版本的 antd,样式叠在一起必然冲突。
问题三:弹窗挂载到 body 的问题
antd Modal、Drawer 等组件为了解决 z-index 层叠和 overflow: hidden 裁剪问题,会把弹窗 DOM 挂到 document.body 下,而不是组件内部。
xml
<body>
<div id="app">
<button>打开弹窗</button>
<!-- 弹窗不在这里 -->
</div>
<div class="ant-modal-root"> ← 弹窗挂在这里
弹窗内容
</div>
</body>
CSS Modules 改的是类名,和挂载位置无关,这个问题它解决不了。
三、qiankun 的两种 CSS 隔离方案及问题
方案一:Shadow DOM(strictStyleIsolation: true)
将子应用挂载节点包在 Shadow DOM 内,样式物理隔离,不穿透边界。
php
start({ strictStyleIsolation: true })
问题:弹窗逃出 Shadow DOM
xml
<body>
<div data-qiankun="app-name">
#shadow-root ← Shadow DOM 边界
子应用内容
<button>打开 Modal</button>
</div>
<div class="ant-modal-root"> ← antd 把弹窗挂到了 Shadow DOM 外面
弹窗内容 ← Shadow DOM 内的样式覆盖不到这里,样式丢失
</div>
</body>
方案二:Scoped CSS(experimentalStyleIsolation: true)
qiankun 扫描子应用所有 style 标签,给每条 CSS 规则动态加属性前缀。
php
start({ experimentalStyleIsolation: true })
css
/* 原始 */
.button { color: red; }
/* 处理后 */
div[data-qiankun="app-name"] .button { color: red; }
三个边界问题:
① 动态插入的样式覆盖不到
qiankun 在 mount 时扫描,antd 弹窗是用户点击后才动态插入 style,时机在扫描之后:
mount 时扫描 style 标签
↓
用户点击按钮
↓
antd 动态插入弹窗样式 ← qiankun 不知道,没有加前缀,全局生效
② :root 语义被破坏
:root 是 html 元素,是整个页面最顶层的节点,常用来定义全局 CSS 变量:
css
:root { --primary-color: #1890ff; }
.button { color: var(--primary-color); }
加前缀后:
css
/* :root 不可能是 div 的后代,这个选择器永远匹配不到任何元素 */
div[data-qiankun="app-name"] :root { --primary-color: #1890ff; }
/* 变量取不到值,主题色失效 */
div[data-qiankun="app-name"] .button { color: var(--primary-color); }
③ @keyframes 引用断裂
css
/* 处理后:keyframes 名加了前缀 */
@keyframes app-name-fadeIn { from { opacity: 0; } to { opacity: 1; } }
/* 但 animation 引用处没有同步改名,找不到动画,直接失效 */
div[data-qiankun="app-name"] .modal { animation: fadeIn 0.3s ease; }
为什么不修这些问题:
:root即使替换成容器选择器,挂到 body 的弹窗仍取不到变量,问题没有根本解决@keyframes需要完整 CSS AST 解析,引入解析器会显著增大包体积、增加 mount 耗时- 动态样式可以用
MutationObserver监听,但插入和回调之间有时间差会闪烁,且CSSStyleSheetAPI 插入的样式监听不到
本质原因:在应用层用正则模拟 CSS 作用域,天花板就在这里,要彻底解决需要浏览器渲染层支持(即 Shadow DOM)。
四、实际项目的解决方案
方案一:指定弹窗挂载容器(解决 Shadow DOM 弹窗问题)
javascript
import { ConfigProvider } from 'antd'
function App() {
return (
<ConfigProvider
getPopupContainer={() => document.querySelector('div[data-qiankun="app-name"]')}
>
<YourApp />
</ConfigProvider>
)
}
❌ 只解决 antd,其他第三方库需单独处理。
方案二:postcss 构建时自动加前缀(推荐)
不依赖 qiankun 运行时处理,构建产物天然带前缀:
javascript
// postcss.config.js
module.exports = {
plugins: [
require('postcss-prefix-selector')({
prefix: '.app-finance',
exclude: [/global.scss/], // 排除真正需要全局的样式
}),
],
}
css
/* 原始 */
.button { color: red; }
body { margin: 0; }
/* 构建后 */
.app-finance .button { color: red; }
.app-finance body { margin: 0; } /* body 也被限定在容器内 */
弹窗、动态样式、@keyframes 都不受影响,因为前缀是编译期加的,不依赖运行时扫描。
❌
:root变量需要改成挂在容器选择器上,团队需要统一规范。
方案三:CSS Modules + postcss 组合(务实选择)
CSS Modules → 解决子应用内部组件间类名冲突
postcss 前缀 → 解决子应用之间全局样式冲突
getPopupContainer → 解决第三方库弹窗样式丢失
关闭 qiankun 自带的 CSS 沙箱,完全由构建工具保证隔离,运行时没有额外开销,边界情况可控。
五、总结对比
| 方案 | 解决组件间冲突 | 解决应用间冲突 | 弹窗问题 | 运行时开销 |
|---|---|---|---|---|
| CSS Modules | ✅ | ❌ | ❌ | 无 |
| qiankun Shadow DOM | ✅ | ✅ | ❌ 样式丢失 | 低 |
| qiankun Scoped CSS | ✅ | ✅ 大部分 | ⚠️ 部分失效 | 中 |
| postcss 前缀 | ✅ | ✅ | ✅ | 无 |
| CSS Modules + postcss | ✅ | ✅ | ✅(需配合 getPopupContainer) | 无 |