必须用 <template> + document.importNode() 安全克隆并挂载到 Shadow DOM 中,禁用 innerHTML 注入;Shadow DOM 是唯一可靠隔离方案,需在 constructor 中调用 attachShadow,样式须显式注入,第三方库初始化需指定 shadowRoot 为根上下文。

直接 innerHTML 注入模板 = 样式污染 + XSS 风险
别用 el.innerHTML = templateString 往主应用里塞子品牌 HTML。它会把所有 <style> 丢进 document.head,把 <script> 放到全局执行,第三方库(比如 .ant-btn)照样生效,data-brand="luxury" 这种属性根本拦不住样式穿透。
常见错误现象:
- 子品牌模板里的
<style>.header{color:#ff6b6b}</style>让整个页面的.header全变色 - 用户输入的评论含
<img src=x onerror=alert(1)>,一渲染就弹窗 - 两个品牌都用了
id="nav-toggle",document.getElementById('nav-toggle')总返回第一个
必须用 <template> + document.importNode() 安全克隆
<template> 不是“自动模块化”开关,它只是浏览器跳过解析的静态容器。真正安全复用的前提是:从 tmpl.content 克隆,且必须用 document.importNode(tmpl.content, true) —— 否则表单状态(<input checked>、<select> 选中项)全丢,cloneNode(true) 不行。
实操要点:
立即学习“前端免费学习笔记(深入)”;
- 模板写法:
<template id="brand-a"><div class="card"><h2 data-bind="title"></h2></div></template> - 克隆必须写成:
const fragment = document.importNode(tmpl.content, true),不能用tmpl.innerHTML - 填充绑定字段时,遍历
fragment.querySelectorAll('[data-bind]')手动赋值,禁用正则替换{{title}}—— 易 XSS,且不支持属性绑定(如data-bind="src") - 挂载用
container.appendChild(fragment),禁用innerHTML +=—— 已绑定的事件监听器会被销毁
隔离靠 Shadow DOM,不是靠 data- 属性
data-brand="luxury" 本身不产生任何隔离效果。它只在你手动写 CSS 选择器(如 [data-brand="luxury"] .btn)时才有用,但 Ant Design 的 .ant-btn、Bootstrap 的 .btn 全部无视这个前缀,照样全局生效。
唯一可靠路径是 Shadow DOM:
- 必须在自定义元素
constructor()中调用this.attachShadow({ mode: 'open' })—— 延迟到connectedCallback就晚了,DOM 已渲染,样式已计算 - 克隆后的
fragment要挂到this.shadowRoot,不是直接 append 到 light DOM - 样式不能塞
this.shadowRoot.innerHTML = '<style>...</style>',要创建<style>元素再appendChild,或用CSSStyleSheet+adoptedStyleSheets - 生产环境选
mode: 'open','closed'会让this.shadowRoot返回null,DevTools 看不到结构,自动化测试查不到节点
第三方脚本初始化总失败?因为默认找 document
像 new Swiper('.swiper') 或 Quill('#editor') 这类库,默认在 document 上查找元素。而 Shadow DOM 内的节点不在 document 树里,结果就是静默失败、组件空白。
解决方法只有两个方向:
- 优先改用支持容器参数的 API:
new Swiper(shadowRoot.querySelector('.swiper')) - 若框架不支持,必须包装初始化逻辑,把
document替换为shadowRoot作为根查询上下文 - 避免
$(document).ready()类钩子 —— 它永远等不到 shadow 内容挂载完成 - 初始化时机要等
shadowRoot已挂载且 DOM 就绪,可用requestAnimationFrame或简单setTimeout(() => {}, 0),不用监听DOMContentLoaded(它不触发在 shadow 内)
最易被忽略的是:Shadow DOM 不重置 html、body 默认样式,也不阻断 CSS 变量继承(如 --primary-color),这些必须显式重置或透传,否则品牌视觉一致性仍会失控。



















