custom elements 本身不提供沙箱隔离能力,唯一可靠隔离手段是 constructor() 中调用 attachshadow({ mode: 'closed' });否则样式、js、事件均失控,且 customelements.define() 仅注册标签,不自动创建 shadow dom 或隔离上下文。

Custom Elements 本身不提供沙箱隔离能力,attachShadow({ mode: 'closed' }) 是唯一能真正启用样式与 DOM 隔离的手段;没调用它,所有“微前端”都是裸奔。
为什么 customElements.define() 不能直接当沙箱用
浏览器只把 customElements.define() 当作标签注册机制,不自动创建 Shadow DOM,也不拦截全局 CSS、不隔离 JS 执行上下文、不管理资源生命周期。常见现象包括:
-
button { color: red }全局样式穿透进子应用,覆盖其按钮颜色 - 两个子应用都定义
.modal-overlay,后加载的 z-index 覆盖前一个,遮罩失效 - 子应用调用
React.render()后,父应用路由切换时组件未卸载,内存持续增长 -
customElements.define('app-a', AppA)在第二次加载时直接报错Failed to execute 'define' on 'CustomElementRegistry': the name "app-a" has already been used
根本原因:Custom Element 是 Web Components 的“外壳”,不是沙箱——它不提供作用域、不控制执行、不清理副作用。
attachShadow({ mode: 'closed' }) 必须在 constructor() 中调用
Shadow DOM 不是可选功能,而是隔离的基础设施。一旦漏掉或延迟调用,整个子应用就暴露在 light DOM 中,样式和事件完全失控。
- 必须在
constructor()中第一优先级调用this.attachShadow({ mode: 'closed' }),不能等到connectedCallback() -
mode: 'closed'是生产环境唯一推荐值:mode: 'open'允许任意脚本通过el.shadowRoot直接篡改内部结构,等于主动放弃隔离 -
mode: 'closed'下el.shadowRoot返回null,但 DevTools 可查看#shadow-root(需开启 “Show user agent shadow DOM”) - CI/CD 应强制校验:所有
attachShadow调用必须含{ mode: 'closed' },否则阻断构建
样式注入必须手动塞进 shadowRoot,不能靠 link 或 import
Shadow DOM 天然阻断外部样式链,但这也意味着子应用的 CSS 如果没显式注入到 shadowRoot,就完全无效。
-
<link rel="stylesheet" href="app.css">写在自定义元素模板里?它会被插入到 light DOM,样式不生效 - 用
import('./app.css')加载成功,但没shadowRoot.appendChild(styleEl)?等于白加 - 把整个 CSS 字符串拼进
shadowRoot.innerHTML = '<style>...</style>'?可行但难维护,且漏掉@import、字体声明、CSS 变量等边界情况 - 推荐做法:在
connectedCallback()中动态创建<style></style>标签并 append 到shadowRoot;高阶场景可用CSSStyleSheet+adoptedStyleSheets(注意兼容性)
connectedCallback 和 disconnectedCallback 是沙箱生命周期的唯二入口
它们不是“初始化/销毁钩子”,而是 DOM 挂载/卸载信号——必须配合状态标记、资源引用和清理逻辑,才能模拟出可控沙箱。
-
connectedCallback会多次触发(如 display 切换、Vue 列表重渲染),需用私有字段防重复:if (this._isMounted) return; this._isMounted = true; - 子应用挂载必须放在这里:动态
import('./app-entry.js')、保存 root 实例、调用mount(container),容器必须是this.shadowRoot -
disconnectedCallback是清理唯一机会,但不可靠(页面刷新时可能不触发):必须在此清除定时器、abort fetch、调用子应用unmount()、移除事件监听器 - 属性初始值不会触发
attributeChangedCallback,必须在connectedCallback中手动读取:this.getAttribute('name')
真正的难点不在写几个回调,而在于每个子应用的资源加载路径、CSS 作用域、JS 执行上下文、以及父应用对这些过程的统一桥接——Custom Elements 提供的是骨架,沙箱得自己一节一节装上去。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











