子应用HTML片段不能直接innerHTML注入,因浏览器不执行其中script、不解析link、内联style逃逸至全局,易致样式污染与XSS风险;正确做法是用DOMParser解析后逐节点处理,结合Shadow DOM closed mode、CSS模块化及构建时前缀隔离。

子应用 HTML 片段不能直接 innerHTML 注入
直接用 fetch() 拿到 HTML 字符串后赋给 element.innerHTML,看似简单,实则埋雷。浏览器不会执行其中的 <script>,也不会解析 <link rel="stylesheet">,更不会处理内联 style 的作用域——这些都会逃逸到全局,污染主应用样式或触发 XSS 风险。
常见错误现象:
- 子应用按钮颜色覆盖主应用的
.btn类 -
console.log(document.body)显示子应用脚本已执行,但 DOM 节点却挂载在document.body下 - 加载后弹窗(如 Modal)出现在整个页面顶层,脱离容器边界
正确做法是:用 DOMParser 解析字符串,再逐节点处理:
- 所有
<style>提取内容,转为CSSStyleSheet实例,通过shadowRoot.adoptedStyleSheets = [sheet]注入(需检查adoptedStyleSheets兼容性) - 所有
<script src="xxx">拦截后用fetch()加载,再在沙箱环境(重绑定this/window/document)中执行 - 避免写
shadowRoot.innerHTML = htmlString—— 这会绕过 Shadow DOM 封装机制
命名空间隔离必须在构建阶段介入
靠运行时“加前缀”(如 micro-app[name=cart] .btn)只能解决部分问题,且对 body、html、伪类、属性选择器无效。真正可靠的隔离,得在打包时就切断全局污染路径。
立即学习“前端免费学习笔记(深入)”;
使用场景:
- 多个子应用都用了 Ant Design,默认
.ant-btn必然冲突 - 主应用设置了
body { font-size: 14px },子应用想用 16px 却被强制继承
实操建议:
- Webpack 构建时启用
css-modules,配合localIdentName生成唯一哈希类名:[name]_[local]_[hash:base64:5] - PostCSS 插件如
postcss-prefix-selector,可统一为所有规则添加前缀,例如将.modal→.app-cart-modal - UI 库必须显式配置
prefixCls(如 Ant Design 的ConfigProvider),否则其全局样式仍会生效 - 禁止在子应用中写
body或html相关样式;改用 CSS 变量 + 容器级 class 控制主题
Shadow DOM 的 closed mode 是硬性要求
写 this.attachShadow({ mode: 'open' }) 等于没隔离。生产环境必须用 { mode: 'closed' },否则外部脚本可通过 el.shadowRoot 直接篡改内部样式表或 DOM 结构,登录框的 type="password" 都可能被改成 text。
容易踩的坑:
- CI/CD 流水线未校验
attachShadow参数,导致上线后隔离失效 - 误以为
mode: 'closed'下无法调试——其实 DevTools 支持显示#shadow-root (closed),只是不开放 JS 访问 - 第三方 Web Component polyfill 依赖
shadowRoot,需提前 patch,否则报错Cannot read property 'appendChild' of null
样式注入必须手动完成:
- 不能在 HTML 中写
<link rel="stylesheet" href="app.css">,它会插入 light DOM,完全无效 - 不能靠
import('./app.css')自动生效——必须在connectedCallback()中创建<style>标签并 append 到this.shadowRoot - 字体声明(
@font-face)、媒体查询、@import需一并提取,否则丢失
iframe 不是万能 fallback,而是权衡后的特例
别把 iframe 当成“样式隔离兜底方案”。它确实天然隔离,但代价明确:内存占用高(Chrome 实测约 Shadow DOM 的 2.3 倍)、iOS Safari 渲染不稳定、跨域通信仅限 postMessage、无法共享状态或事件总线。
只在以下情况考虑 iframe:
- 子应用是第三方不可信服务(如银行支付页、广告 SDK)
- 子应用技术栈完全异构(比如纯 jQuery + 后端模板渲染),且无改造意愿
- 团队缺乏 JS 沙箱与 Shadow DOM 维护能力,宁可牺牲性能换稳定性
使用前提:
-
src必须是合法 URL(http://或/path),file://和空字符串必白屏 - 本地开发必须起 HTTP 服务,
python3 -m http.server 8000是最低成本方案 - 必须显式设置宽高:
style="width: 100%; height: 400px; border: none;",否则在 Flex/Grid 中塌陷 - 禁用滚动条请用父容器
overflow: hidden,scrolling="no"已废弃
真正的难点不在怎么选方案,而在如何让不同团队遵守同一套样式契约:比如约定所有子应用禁止使用 !important、禁止修改 html/body、所有组件必须导出 mount 函数且接受容器参数——这些细节漏掉一个,隔离就形同虚设。



















