结论:仅靠标签无法实现样式隔离,必须结合closed shadow dom才能真正隔离。因为只是静态容器,插入后所有样式和dom仍属light dom,第三方规则照常生效;唯有attachshadow({mode:'closed'})并注入shadowroot内部样式,才能切断穿透与查询链。

直接说结论:仅靠<template></template>标签本身,完全无法隔离样式——它只是浏览器跳过解析的静态容器,插入后所有 class、id、style 全部落到 light DOM,第三方库规则照常生效,主应用 CSS 照样穿透。
为什么<template></template>不等于样式隔离
很多人把子组件 HTML 塞进<template id="card"></template>,就以为“模块化”了。其实它既不阻止外部button { color: red }生效,也不防.modal类名撞车。常见错误现象包括:
- 子模板里写
<div class="header">,结果被主应用的<code>.header { margin-top: 20px }改了间距 - 多个团队共用同一份
<template></template>,但各自填充后id="submit"重复,document.getElementById('submit')返回错节点 - 模板含
<script>console.log('init')</script>,克隆插入后脚本根本不会执行 - 必须从
tmpl.content克隆,不能用tmpl.innerHTML——后者丢失select选项、textarea值、input的checked/disabled状态 - 克隆后需遍历
fragment.querySelectorAll('[data-bind]')手动填充,别用正则替换{{title}}——易 XSS,且无法处理属性绑定(如data-bind="src") - 挂载时用
container.appendChild(fragment),禁用innerHTML +=——否则已挂载的事件监听器会被销毁 - 必须在
constructor()中调用:this.attachShadow({ mode: 'closed' });延迟到connectedCallback就晚了,污染已发生 - 禁止用
mode: 'open'——它让this.shadowRoot可被任意脚本访问,等于放弃隔离 - 样式必须注入
shadowRoot内部:this.shadowRoot.appendChild(Object.assign(document.createElement('style'), { textContent: cssText }));往document.head插<link>或<style></style>完全无效
document.importNode()是安全克隆<template></template>的唯一方式
cloneNode(true)会丢掉表单控件状态(如<input checked>的选中态),而document.importNode(tmpl.content, true)能保留原生行为,兼容 Shadow DOM 场景。
真隔离必须配合attachShadow({ mode: 'closed' })
<template></template>本身无作用域,只有把它克隆后的 fragment 插入到 closed Shadow DOM 中,才能切断样式穿透和 DOM 查询链。
真正容易被忽略的是:哪怕你用了data-team="a"、BEM 类名、甚至 PostCSS 前缀,只要没走 Shadow DOM,所有这些都只是“防撞车”,不是“防穿透”。第三方 UI 库(如 Ant Design)的.ant-btn规则依然生效,body * { font-size: 14px }照样继承进来——这是作用域缺失,不是选择器写得不够狠。











