From fb042309589190fbbb5a3b423f29954f0e8d6e9c Mon Sep 17 00:00:00 2001 From: cirry <812852553@qq.com> Date: Sun, 21 Jun 2026 20:19:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E5=86=B2=E7=AA=81=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E8=A7=A3=E5=86=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/micro-app多子应用接入实战.md | 226 +++++++++++++ docs/micro-app面试题.md | 526 +++++++++++++++++++++++++++++- src/App.vue | 39 ++- src/config/subApps.ts | 19 +- 4 files changed, 780 insertions(+), 30 deletions(-) diff --git a/docs/micro-app多子应用接入实战.md b/docs/micro-app多子应用接入实战.md index 6570649..ade2d30 100644 --- a/docs/micro-app多子应用接入实战.md +++ b/docs/micro-app多子应用接入实战.md @@ -2172,6 +2172,232 @@ Webpack 子应用 → iframe: false + 默认 scoped (性能好 + 样式安 --- +### Q31:已经发现了样式冲突问题,如何修复?有哪些方案? + +**答:** +修复方案从简单到体系化分为四层。最简单的方案(也是我们实际采用的)只需**删掉一行配置**。 + +--- + +#### 一、方案一:恢复样式隔离(最简单,一行改动) + +**根因回顾:** + +```ts +// ❌ 冲突的配置 +{ + name: 'mock-app', + iframe: false, + disableScopecss: true, // ← 这行是罪魁祸首 + keepAlive: true, +} +``` + +**修复:删除 `disableScopecss: true`(恢复默认值 `false`)** + +```ts +// ✅ 修复后的配置 +{ + name: 'mock-app', + iframe: false, + // disableScopecss 不设置,默认为 false + // → micro-app 自动为子应用 CSS 添加作用域前缀 + keepAlive: true, +} +``` + +**一行改动,问题解决。** + +--- + +#### 二、修复原理:micro-app 的 CSS 作用域机制 + +当 `disableScopecss: false`(默认)时,micro-app 在注入子应用的 CSS 时,自动给每个选择器加上属性选择器前缀: + +``` +子应用原始 CSS: +───────────────────────────────────────────── + h2 { color: #ff6b35; border-left: 6px solid orange; } + button { background: #ff6b35; } + p { font-size: 13px; } + + ↓ micro-app 自动转换 ↓ + +注入主文档后的 CSS: +───────────────────────────────────────────── + micro-app[name=mock-app] h2 { + color: #ff6b35; + border-left: 6px solid orange; + } + micro-app[name=mock-app] button { + background: #ff6b35; + } + micro-app[name=mock-app] p { + font-size: 13px; + } +``` + +**效果:** + +| 选择器 | 作用范围 | +|--------|----------| +| 主应用的 `h2 { color: purple }` | 整个文档 | +| 子应用转换后的 `micro-app[name=mock-app] h2 { color: orange }` | 只作用于 `` 内部的 h2 | + +**两个选择器不再冲突** — 它们选择的是不同 DOM 范围内的元素。特异性不同,作用范围不同,互不干扰。 + +--- + +#### 三、修复前后对比 + +``` +修复前 (disableScopecss: true): +─────────────────────────────────────────────── +文档中的 CSS: + h2 { color: purple; border-bottom: 2px solid purple; } ← 主应用 + h2 { color: orange; border-left: 6px solid orange; } ← mock-app + h2 { color: teal; border-right: 5px solid teal; } ← mock-app-2 + +结果: 三个 h2 规则搅拌在一起,逐属性混搭 + → 紫色底边框 + 橙色左边框 + 青色右边框 同时出现 ❌ + +修复后 (disableScopecss: false): +─────────────────────────────────────────────── +文档中的 CSS: + h2 { color: purple; ... } ← 主应用(全局) + micro-app[name=mock-app] h2 { color: orange; ... } ← 仅 mock-app 内 + micro-app[name=mock-app-2] h2 { color: teal; ... } ← 仅 mock-app-2 内 + +结果: 主应用的 h2 是紫色 + mock-app 内的 h2 是橙色 ✅ + mock-app-2 内的 h2 是青色 ✅ + → 各管各的区域,零冲突 ✅ +``` + +--- + +#### 四、修复验证方法 + +``` +1. 修复前先看一次: + 访问 /mock-app → F12 → Elements → +

子应用标题

← 样式天然隔离,内外互不穿透 +
+``` + +--- + +**四、三种隔离方案对比(CSS 前缀 vs Shadow DOM vs iframe)** + +| | CSS 前缀改写 | Shadow DOM | iframe 沙箱 | +|---|---|---|---| +| **配置** | 默认,无需配置 | `shadowDOM: true` | `iframe: true` | +| **样式隔离方向** | 子→主 ✅ / 主→子 ❌ | 双向 ✅ | 双向 ✅ | +| **JS 隔离** | 依赖沙箱配置 | 依赖沙箱配置 | ✅ 原生隔离 | +| **ES Module** | 不支持(with 沙箱) | 不支持(with 沙箱) | ✅ 支持 | +| **DOM 树** | 共享 | 独立 Shadow Tree | 独立 Document | +| **通信** | 同 window,直接引用 | 同 window,直接引用 | postMessage,序列化 | +| **兼容性** | 最好 | 部分 UI 库不兼容 | 最好 | +| **全局样式污染** | 会(主→子) | 不会 ✅ | 不会 ✅ | +| **适用场景** | Webpack 子应用 | 纯样式隔离需求 | Vite 子应用、完整隔离 | + +``` +隔离强度递增 → + + CSS 前缀改写 Shadow DOM iframe 沙箱 + ┌──────────┐ ┌──────────────┐ ┌──────────────┐ + │ 子→主 ✅ │ │ 子→主 ✅ │ │ 子→主 ✅ │ + │ 主→子 ❌ │ │ 主→子 ✅ │ │ 主→子 ✅ │ + │ JS 部分 │ │ JS 部分 │ │ JS 完全 ✅ │ + │ DOM 共享 │ │ DOM 隔离 │ │ 全部隔离 │ + └──────────┘ └──────────────┘ └──────────────┘ +``` + +**选择决策树:** + +``` +子应用是 Vite 构建? + ├── 是 → iframe: true(必须,ES Module 限制) + └── 否(Webpack) + ├── 只需样式隔离 → shadowDOM: true(可选) + ├── 需完整 JS 隔离 → iframe: true + └── 隔离要求不高 → 默认配置(CSS 前缀改写) + └── 注意主→子穿透!主应用样式需用 :not() 排除子应用容器 +``` + +--- + +### Q2-1:为什么 CSS 前缀改写只能阻止「子→主」而不能阻止「主→子」? + +**答:** + +这是 CSS 前缀改写的**单向性**决定的: + +``` +子→主(子应用样式泄漏到主应用): + 子应用 CSS: h2 { color: orange } + ↓ micro-app 改写 + 改写后: micro-app[name=mock-app] h2 { color: orange } + ↓ + 主应用的 h2 的父级链中没有 micro-app[name=mock-app] → 不匹配 ✅ + +主→子(主应用样式泄漏到子应用): + 主应用 CSS: .app-main h2 { border-bottom: 2px solid purple; } + ↓ 没有任何改写!原样生效 + 子应用 h2 的祖先链中有 .app-main → 匹配 ❌ +``` + +**核心原因**:micro-app 只改写**子应用的 CSS**,不改写主应用的 CSS。主应用的样式规则在全 document 范围内照常匹配,而 `iframe: false` 模式下子应用 DOM 就在这个 document 里。 + +**三种解决思路**: +1. 主应用 CSS 加 `:not(.child-app-wrapper)` 排除子应用容器(本项目方案) +2. 开启 `shadowDOM: true` — Shadow DOM 边界双向阻断 +3. 开启 `iframe: true` — 独立 document,双向天然隔离 --- @@ -191,6 +355,102 @@ micro-app 选择 `new Function()` 的原因: --- +### Q8-1:为什么 Vite 子应用必须用 `iframe: true`,而 Webpack 子应用不需要? + +**答:** + +核心原因在于 Vite 和 Webpack 输出的 JS 代码**格式不同**,而 micro-app 的 with 沙箱只能执行某种格式。 + +**一、开发环境(Dev Server)的差异** + +| | Vite Dev Server | Webpack Dev Server | +|---|---|---| +| 输出的 JS 格式 | ` + + + + + + + + +``` + +**二、with 沙箱的执行过程** + +micro-app 用 `new Function()` 执行子应用的 JS: + +```ts +// micro-app 内部简化逻辑(with 沙箱模式) +const code = await fetchJsFromChildApp() // 拉取子应用的 JS 文本 + +// ⚠️ 这里执行 JS +const fn = new Function('window', ` + with(window) { + ${code} // ← 子应用的代码直接拼接在这里 + } +`) +fn(microWindow) // microWindow 是 Proxy 代理的隔离 window +``` + +**问题来了:** 如果 `code` 包含 `import`/`export`: + +```js +// 子应用代码 +import { createApp } from 'vue' // ❌ SyntaxError! +// ↑ import 只能出现在模块顶层,不能出现在 new Function() 内部 +``` + +浏览器的模块系统要求 `import` 必须出现在 `