最安全的微前端子应用集成方式是使用 domparser 解析 html 并在 constructor 中同步调用 attachshadow({ mode: 'closed' }),配合构建时绝对路径配置与样式前缀处理。

子应用 HTML 不能直接 innerHTML 注入容器
直接 container.innerHTML = htmlString 是最常见、也最危险的集成方式。浏览器会跳过沙箱执行所有 <script></script>,把 <style></style> 插入 document.head,导致样式逃逸、脚本污染全局 window,甚至触发 XSS。
常见现象包括:子应用弹窗挂到 document.body、主应用字体被 normalize.css 重置、控制台报 Failed to execute 'querySelector' on 'Document': The provided selector is empty(因脚本在全局执行但 DOM 尚未挂载)。
- 必须用
new DOMParser().parseFromString(html, 'text/html')解析字符串,得到标准Document对象 - 只取
doc.body内容作为挂载片段,丢弃原始doc.head—— 所有<link rel="stylesheet">和内联<script></script>都需单独处理 - 禁止对解析后的 fragment 使用
innerHTML +=或insertAdjacentHTML,否则已绑定的事件监听器会被销毁
自定义元素必须在 constructor 中调用 attachShadow({ mode: 'closed' })
customElements.define('sub-app', MyApp) 本身不创建任何隔离边界。DOM 仍插入 light DOM,所有外部 CSS(如 button { color: red })和子应用自己的全局规则(如 .header)都会穿透进来。
延迟到 connectedCallback() 再调用 this.attachShadow() 已无意义:此时样式已计算、节点已渲染、污染已发生。
- 必须在
constructor()中同步执行this.attachShadow({ mode: 'closed' })—— 这是唯一能阻止初始样式污染的时机 -
mode: 'open'在生产环境等于放弃隔离:el.shadowRoot可被任意脚本读写,登录框的input[type="password"]都可能被篡改 -
mode: 'closed'下el.shadowRoot返回null,这是安全代价,不是 bug;DevTools 中查看需开启 “Show user agent shadow DOM”
HTML Entry 中的相对路径资源必须转为绝对 URL
子应用独立运行时 src="./main.js" 没问题,但被主应用 fetch() 后执行时,当前 base 是主应用域名,相对路径会拼错成 https://main.com/./main.js,直接 404。
构建阶段设 publicPath: 'auto' 不可靠:Webpack 5+ 的 auto 检测依赖 document.currentScript,而微前端中该脚本不存在或指向主应用。
- 子应用 Webpack 构建时应显式配置
publicPath: 'https://sub.example.com/'(带协议和完整域名) - 主应用运行时可用
new URL(attrValue, subAppEntryUrl).href重写所有src/href属性,比正则更健壮 - 禁止子应用内联脚本直接操作
window,如<script>window.myLib = {}</script>会绕过沙箱,污染全局
第三方 UI 库的全局样式必须显式约束
Ant Design、Element Plus 等库默认注入的 .ant-btn、body { margin: 0 } 规则,不受 scoped 样式或 CSS Modules 限制,会直接作用于全局 html 和 body 节点。
仅靠 data-subapp="a" 属性 + 运行时前缀(如 micro-app[name="a"] .ant-btn)无法覆盖 body、伪类、属性选择器,且对 @import 引入的样式无效。
- 子应用必须启用 UI 库的
prefixCls配置(如 Ant Design 的ConfigProvider),避免使用默认类名 - 禁止在子应用中写
body或html相关样式;改用 CSS 变量 + 容器级 class 控制主题 - 构建阶段必须介入:用 PostCSS 插件(如
postcss-prefix-selector)统一为所有规则加前缀,或启用 Webpackcss-modules并设localIdentName
this.attachShadow({ mode: 'closed' })、DOMParser 解析后的逐节点分治、以及构建时就切断全局路径的硬性约束——漏掉任一环,隔离墙就从内部裂开。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











