customElements.define() 本身不隔离样式,必须在 constructor() 中调用 this.attachShadow({ mode: 'closed' }) 才能建立真正样式隔离边界,延迟至 connectedCallback() 会导致初始样式污染;mode: 'open' 在生产环境等于放弃隔离。

customElements.define() 本身不隔离样式,必须配合 attachShadow({ mode: 'closed' })
直接调用 customElements.define('my-app', MyApp) 只是注册标签名,DOM 仍插入 light DOM,所有外部 CSS(如 button { color: red })和子应用自己的全局样式(如 .header)都会穿透进来。常见现象包括:子应用的 normalize.css 重置了主应用 body 字体大小;父应用的 [data-theme="dark"] .btn 意外命中子应用按钮;两个子应用都定义 .modal-overlay,后加载的覆盖前者 z-index。
真正起作用的只有 this.attachShadow({ mode: 'closed' }),且必须在 constructor() 中调用——延迟到 connectedCallback() 已无法阻止初始样式污染。CI/CD 流水线应强制检查该调用是否含 { mode: 'closed' },否则阻断构建。
mode: 'open' 在生产环境等于放弃隔离:外部脚本可通过 el.shadowRoot 直接修改内部样式或 DOM 节点,比如篡改 input[type="password"] 属性。DevTools 中虽可查看 #shadow-root(需开启“Show user agent shadow DOM”),但 mode: 'closed' 下 el.shadowRoot 返回 null,这是安全代价,不是 bug。
HTML 中的 data-* 属性不是隔离开关,只是选择器前缀载体
data-subapp="a" 这类属性本身不提供任何样式封装能力。它只在配合构建时重写或运行时注入时才起作用,比如 qiankun 的 strictStyleIsolation: true 会在容器上加 data-qiankun="sub-app-a",再把所有 .btn 规则重写为 [data-qiankun="sub-app-a"] .btn;MicroApp 则自动以 micro-app[name="xxx"] 为选择器前缀。
立即学习“前端免费学习笔记(深入)”;
单独写 <div data-subapp="b"></div> 或注册自定义标签却不调用 attachShadow(),和普通 div 完全等价。第三方库(如 Ant Design)的 .ant-btn 依然全局生效,data- 对它无效。你手动加的 data- 属性若没被框架识别、没参与 CSS 重写流程,就只是个无意义的 HTML 属性。
真正起效的组合是:data- + 构建时前缀(如 PostCSS 插件 postcss-css-namespace) + 运行时注入(如 qiankun 动态改写 style 标签内容)。但注意:@import、url() 引用的字体或图片路径可能漏掉前缀,需额外处理。
不能用 shadowRoot.innerHTML = htmlString 注入子应用 HTML
直接赋值会绕过 Shadow DOM 封装机制:<style></style> 内容逃逸到全局作用域,<script></script> 在全局上下文中执行,既污染样式又带来 XSS 风险。典型错误现象包括:子应用 Ant Design 样式覆盖主应用按钮颜色;控制台报错 Failed to execute 'querySelector' on 'Document': The provided selector is empty(因脚本在全局执行但 DOM 尚未挂载)。
正确做法是用 DOMParser 解析 HTML 字符串,再分治处理:
- 提取所有
<style></style>内容,转为CSSStyleSheet实例,通过shadowRoot.adoptedStyleSheets = [sheet]注入(Chrome 73+ / Safari 16.4+ 支持) - 拦截
<script src=""></script>,fetch 后在沙箱中执行(重绑定this/window/document到当前 shadow 上下文) - 剥离内联
<script></script>,避免意外执行
adoptedStyleSheets 不支持 @import 和相对路径资源,字体声明、动画定义需手动克隆注入;<slot></slot> 内容样式仍受外部 CSS 影响,必要时加 all: initial 重置。
iframe 是唯一原生 JS 隔离方案,但开销大且通信受限
如果子应用是完全不可信的第三方服务(如银行支付页、广告 SDK),或你明确接受其全部代价,才选 iframe。它天然隔离 document、window、history 等 API,但代价是独立渲染进程、资源重复加载、DOM 无法直连、SEO 友好性差。
iframe.src 必须是合法可加载地址:空字符串、javascript:void(0)、file:// 协议均被浏览器静默拒绝;本地开发必须起 HTTP 服务(如 python3 -m http.server 8000);生产环境推荐绝对路径(如 /micro/cart.html);动态赋值前需校验协议(url.startsWith('http') || url.startsWith('/'))。
跨域时 iframe.contentDocument 和 iframe.contentWindow.document 不可用,所有通信只能走 postMessage(),且双方必须校验 event.origin。未设宽高或在 Flex/Grid 容器中,iframe 易塌陷消失,必须显式设置 style="width: 100%; height: 400px; border: none;"。



















