真正实现文档结构硬隔离的只有iframe或attachShadow({mode:'closed'})配合严格解析流程,innerHTML=htmlString会破坏隔离,因内联style和script被提升至全局执行。

硬隔离不是靠加个data-app属性或者套个<div class="subapp">就能成立的。真正起效的只有两条路径:用iframe,或者用attachShadow({ mode: 'closed' })配合严格的内容解析流程。其他所有“伪隔离”手段,在真实业务中都会在某个节点上漏出样式、脚本或DOM查询污染。
为什么innerHTML = htmlString是微前端HTML注入的致命错误
直接把含<style>和<script>的HTML字符串赋给shadowRoot.innerHTML,等于主动拆掉隔离墙。浏览器会把内联<style>提升到全局document头部,把<script>放在全局上下文中执行——子应用调用document.body.classList.add('loading'),主应用整个页面就卡住。
- 常见错误现象:
Ant Design的.ant-btn规则穿透进子应用,按钮圆角/边框全乱;控制台报Failed to execute 'querySelector' on 'Document': The provided selector is empty(因脚本在全局执行但DOM尚未挂载) - 根本原因:
innerHTML绕过Shadow DOM的封装机制,不触发样式作用域绑定,也不重绑定JS执行上下文 - 替代方案必须分治:
DOMParser解析后,<style>转CSSStyleSheet实例,再通过shadowRoot.adoptedStyleSheets = [sheet]注入;<script src="">拦截fetch后沙箱执行;内联<script>直接剥离禁止执行
template标签不是隔离容器,只是静态占位符
<template id="login"><div class="header">...</div></template>本身不提供任何作用域边界。你用cloneNode(true)或innerHTML把它塞进主应用DOM,所有class、id、内联样式照常被主应用CSS穿透,document.getElementById('modal')照样返回错对象。
- 必须用
document.importNode(tmpl.content, true)克隆——它能保留<input checked>状态、<select>选中项、<textarea>值,cloneNode做不到 - 克隆后不能直接
appendChild到light DOM,必须先挂到closedShadow DOM里:host.attachShadow({ mode: 'closed' })→shadowRoot.appendChild(fragment) - 模板内禁用硬编码
id,改用data-id或动态生成唯一标识,避免多个子应用重复id="modal"导致DOM查询失效
mode: 'closed'必须在constructor()中调用,晚一秒就失效
Custom Element注册后,若在connectedCallback甚至attributeChangedCallback里才调用this.attachShadow({ mode: 'closed' }),DOM早已渲染、样式早已计算完毕——污染不可逆。此时哪怕补上Shadow DOM,也拦不住已逃逸的<style>和已执行的全局脚本。
立即学习“前端免费学习笔记(深入)”;
-
mode: 'open'等于开门揖盗:外部JS可通过el.shadowRoot直接读写内部DOM、篡改input[type="password"]、注入恶意CSS规则 - CI/CD流水线必须强制校验:所有
attachShadow调用都带{ mode: 'closed' },否则阻断构建 -
mode: 'closed'下el.shadowRoot返回null,DevTools虽显示#shadow-root节点,但无法用JS直接访问——这是安全代价,不是缺陷
真正难的不是写对那几行attachShadow,而是让整个子应用的构建流程、资源加载、脚本执行、样式引用全部适配这套隔离链路。比如adoptedStyleSheets不支持@import和相对路径url(./icon.png),这些必须在构建时重写或预加载;比如第三方库弹窗默认挂载到document.body,得手动指定getContainer到Shadow根节点。漏掉任意一环,隔离就形同虚设。



















