5月31日 17:12

Garfish 样式隔离怎么做?Shadow DOM 和 scoped CSS 如何取舍?

Garfish 的样式隔离不是把 CSS 变成“绝对安全”,而是给每个子应用划出清晰作用域,减少全局 reset、组件库类名、运行时 style 标签互相覆盖。实际落地时,最常见的是 scoped CSS、Shadow DOM 和动态样式清理三类方案。它们解决的问题不同,取舍也不同:scoped CSS 兼容性好,Shadow DOM 隔离更强,动态清理负责避免卸载后的残留污染。

scoped CSS 适合多数业务系统

scoped CSS 通常通过构建工具给选择器加容器前缀,例如把 .button 变成 [data-garfish-app="order"] .button。它不改变 DOM 结构和事件模型,对 Ant Design、Element Plus 这类组件库比较友好,所以中后台系统优先选它。边界是它只能处理被构建工具改写过的样式,管不住第三方库运行时直接插到 document.head 的全局样式。

js
module.exports = { plugins: { 'postcss-prefix-selector': { prefix: '[data-garfish-app="order"]', transform(prefix, selector) { if (/^(html|body)/.test(selector)) return selector return `${prefix} ${selector}` } } } }

Shadow DOM 隔离更强,但更挑场景

Shadow DOM 使用浏览器原生边界,外部样式不容易影响子应用,子应用样式也不容易泄漏出去。它适合边界很清楚的独立模块,比如嵌入式配置台、低频运营后台或独立小工具。踩坑点是弹窗、Tooltip、全局 message、主题变量和 E2E 选择器都可能要额外适配;如果组件库大量依赖 document.body 挂载弹层,Shadow DOM 的维护成本会明显上升。

js
const host = document.querySelector('#subapp') const root = host.attachShadow({ mode: 'open' }) root.appendChild(document.createElement('div'))

卸载清理决定长期稳定性

很多线上样式问题不是首次加载发生,而是切换几次子应用后才出现。原因通常是旧应用的 <style><link>、弹层 DOM 没有移除。Garfish 可以管理部分资源生命周期,但子应用仍应在 unmount 中清理自己创建的节点和组件库全局实例。

js
const links = [] export function mount() { const link = document.createElement('link') link.rel = 'stylesheet' link.href = '/order/index.css' document.head.appendChild(link) links.push(link) } export function unmount() { links.splice(0).forEach(node => node.remove()) }

追问

scoped CSS 和 Shadow DOM 应该怎么选?

多数业务系统先选 scoped CSS,因为它改造成本低,和现有组件库、埋点、自动化测试的冲突少。Shadow DOM 更适合子应用边界稳定、弹层少、主题依赖少的场景。取舍点不是谁更先进,而是隔离强度和接入成本能否平衡。

为什么弹窗样式经常隔离失败?

很多组件库默认把 Modal、Popover、Message 挂到 document.body,它已经脱离了子应用容器。scoped CSS 的前缀匹配不到它,Shadow DOM 内部样式也覆盖不到它。解决时要统一配置弹层容器,例如 getPopupContainer,不要只修某一个组件。

主应用主题变量要不要给子应用用?

如果主题是平台能力,应该用 CSS 变量透传,例如 --color-primary--font-size-base。但主应用不要直接覆盖子应用内部类名,否则会形成隐性耦合。边界是共享稳定 token,不共享具体 DOM 结构。

样式隔离会影响性能吗?

scoped CSS 的主要成本在构建期,运行时影响通常很小。Shadow DOM 也不是主要瓶颈,真正要注意的是重复加载组件库样式和卸载后残留节点。不要为了省一点 CSS 体积放弃隔离,否则线上排查污染更贵。

标签:Garfish