class属性本身不提供样式隔离,真正隔离需依赖Shadow DOM或运行时前缀等机制;它仅作为CSS选择器目标,无法阻断级联,易受全局规则穿透影响。

class 属性本身不提供任何样式隔离能力
class 是 HTML 全局属性,浏览器对它的处理完全中立:它只是 CSS 选择器的匹配目标,不自带作用域、不触发封装、不阻止级联。在微前端里写 <div class="btn">,和写 <div> 没区别——只要有一条全局规则 .btn { color: red },它就生效,不管这个 div 是主应用还是子应用的。
常见错误现象包括:
- 子应用用了 Ant Design,其
.ant-btn被父应用的body * { font-size: 12px }穿透影响 - 两个子应用都定义了
.modal,后加载的覆盖前一个的z-index - 子应用的
normalize.css重置了整个页面的body字体大小
这跟 class 写在哪、怎么命名都没关系。BEM、CSS Modules、甚至加前缀如 subapp-a-btn,都只是“降低冲突概率”,不是“阻断样式链”。
真正起效的隔离必须切断 CSSOM 的级联路径
唯一能物理阻断样式穿透的原生机制是 Shadow DOM,且必须满足两个硬条件:
立即学习“前端免费学习笔记(深入)”;
- 在自定义元素的
constructor()中调用this.attachShadow({ mode: 'closed' }) - 所有子应用 DOM 必须挂载进该
shadowRoot,不能直接插入 light DOM
mode: 'closed' 是关键:它让外部脚本无法访问 shadowRoot,浏览器也拒绝将 light DOM 的样式规则注入 shadow tree。此时哪怕父应用写了 body .btn,也匹配不到 shadow 内部的 class="btn" 节点。
但要注意:
-
class在 shadow 内依然有效,只是作用域被限制在 shadow tree 内部 - 第三方库(如 Ant Design)若未适配 Shadow DOM,会把弹窗、Tooltip 挂到
document.body,导致样式逃逸 -
adoptedStyleSheets是注入样式的推荐方式,但 IE 和部分旧版 Safari 不支持,需降级 fallback
运行时加前缀(如 micro-app[name="xxx"] .btn)只是妥协方案
qiankun 的 strictStyleIsolation: true 或 MicroApp 的默认行为,本质是把子应用所有 .btn 规则重写为带容器属性前缀的形式。但它有明确边界:
- 对
body、html、伪类(:hover)、属性选择器([type="text"])无效 - 无法拦截子应用动态插入的
<style>或<link rel="stylesheet">,除非主应用主动解析并重写 - 若子应用用了
@import或url(./icon.png),路径前缀可能漏掉,需额外处理
更隐蔽的问题是:这种方案依赖子应用所有样式都经过构建工具处理。如果子应用直接内联 <style>.btn{color:red}</style>,而主应用没在运行时提取并重写,那这条规则就照常全局生效。
最容易被忽略的污染点:子应用越界操作 DOM
90% 的样式残留问题,不是因为 class 没加前缀,而是子应用自己把样式塞到了不该去的地方:
- 调用
document.head.appendChild(styleEl)动态加载主题 CSS - UI 库(如 Ant Modal)默认挂载到
document.body,连带其<style>标签一起逃逸 - 子应用 HTML Entry 中含
<style>body { margin: 0 }</style>,DOMParser 解析后直接挂进容器,卸载时未清理
这些操作绕过了所有基于容器的隔离逻辑。验证方法很简单:切换子应用后,打开 DevTools 查看 document.styleSheets 列表,如果里面还存在不属于当前子应用的 CSSStyleSheet 实例,说明卸载不干净。
真正的隔离不是靠改 class 名,而是让子应用的所有 DOM 操作(包括样式注入)都被约束在同一个根节点之下——要么靠 shadowRoot 的天然边界,要么靠主应用统一接管 inject/remove 接口,并强制第三方库配置 getContainer。



















