本身不提供样式隔离,必须配合attachshadow({mode:'closed'})才能实现真正隔离;克隆后应使用shadowroot.appendchild而非innerhtml,并通过adoptedstylesheets动态注入样式。

直接把 <template></template> 里的 HTML 插入 DOM,样式必然泄漏——它不带任何隔离能力,只是个静态容器。
为什么 <template></template> 本身不提供样式隔离
浏览器对 <template></template> 的处理是“跳过解析、不执行、不渲染”,但它不创建作用域边界。你用 cloneNode(true) 或 innerHTML 把内容倒进主 DOM 后,所有 class、id、<style></style> 全部暴露在 light DOM 中,和主应用共享同一份 CSSOM。
- 子模板里写
<div class="header">,会被主应用的 <code>.header { margin-top: 20px }改动间距 - Ant Design 的
.ant-btn规则依然全局生效,data-app="cart"完全拦不住 -
<script>console.log('init')</script>在克隆后插入时根本不会执行(innerHTML不触发脚本) - 必须在自定义元素的
constructor()中调用this.attachShadow({ mode: 'closed' }) - 克隆模板内容后,用
shadowRoot.appendChild(fragment)挂载,而非innerHTML -
mode: 'closed'是生产环境唯一推荐值:外部无法读取shadowRoot,避免样式表被篡改或 DOM 被注入 - DevTools 中仍可查看
#shadow-root(需开启“Show user agent shadow DOM”) - 正确做法:用
DOMParser解析 HTML 字符串 → 提取所有<style></style>的textContent→ 创建new CSSStyleSheet()→ 调用sheet.replaceSync(text)→ 最终赋值给shadowRoot.adoptedStyleSheets = [sheet] - 注意兼容性:
adoptedStyleSheets在 Chrome 73+ / Safari 16.4+ 稳定支持,旧版需 fallback 到document.createElement('style')+appendChild - 禁用
@import和相对路径(如url(./img.png)),它们在adoptedStyleSheets中不解析 - 所有
id改为data-id或动态生成唯一值(如data-id="${Date.now()}-${Math.random().toString(36).substr(2, 9)}") - 克隆后遍历
fragment.querySelectorAll('[data-bind]'),逐个设置.textContent或.value,保留原生控件状态(input.checked、select.selectedIndex等) - 挂载必须用
container.appendChild(fragment),禁用innerHTML +=—— 否则已绑定的事件监听器会被销毁
必须配合 attachShadow({ mode: 'closed' }) 才算真正隔离
<template></template> 是内容载体,attachShadow 才是隔离开关。没这句,等于没建墙;延迟到 connectedCallback 再调用,污染早已发生。
动态注入样式必须绕开 innerHTML,走 CSSStyleSheet + adoptedStyleSheets
把整段含 <style></style> 的 HTML 字符串塞进 shadowRoot.innerHTML,会导致样式逃逸到全局,且无法通过 JS 动态修改规则。
模板内禁止硬编码 id,状态绑定不能靠字符串替换
<template></template> 是复用起点,不是最终输出。硬编码 id="modal" 会导致多个实例冲突;用正则替换 {{title}} 会丢状态、引入 XSS 风险。
真正容易被忽略的是:即使用了 attachShadow,只要你在 shadowRoot 里又用 innerHTML 插入含 <style></style> 的字符串,或者从 document.head 动态追加样式,隔离就形同虚设。











