样式冲突问题解决
This commit is contained in:
@@ -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 }` | 只作用于 `<micro-app name="mock-app">` 内部的 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 → <style> 标签
|
||||||
|
→ 看到 h2 { ... } 没有前缀 ← 这是冲突的根源
|
||||||
|
|
||||||
|
2. 修复后再看一次:
|
||||||
|
刷新页面 → F12 → Elements → <style> 标签
|
||||||
|
→ 看到 micro-app[name=mock-app] h2 { ... } ← 自动加了前缀!
|
||||||
|
→ 主应用的紫色底边框不再出现在子应用的 h2 上 ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 五、多层防御体系(从简单到彻底)
|
||||||
|
|
||||||
|
| 层级 | 方案 | 适用场景 | 效果 |
|
||||||
|
|------|------|----------|------|
|
||||||
|
| **L1: 框架层** | `disableScopecss: false`(默认) | 所有 `iframe: false` 的子应用 | micro-app 自动加前缀,零成本 |
|
||||||
|
| **L2: 构建层** | `iframe: true` | Vite 子应用(必须)或高隔离需求 | 浏览器原生 iframe 隔离,最强 |
|
||||||
|
| **L3: 组件层** | Vue SFC `<style scoped>` | 子应用内部组件 | 组件级隔离,防止子应用内部样式冲突 |
|
||||||
|
| **L4: 规范层** | CSS Modules / BEM 命名 | 全局样式、公共组件 | 命名空间隔离,防止类名冲突 |
|
||||||
|
|
||||||
|
**推荐策略:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Vite 子应用:
|
||||||
|
L2 (iframe: true) → 物理隔离,必选 ✅
|
||||||
|
L3 (Vue scoped) → 组件级隔离,推荐 ✅
|
||||||
|
|
||||||
|
Webpack / 普通 HTML 子应用:
|
||||||
|
L1 (disableScopecss: false) → 框架自动隔离,必选 ✅
|
||||||
|
L3 (Vue scoped / BEM) → 额外防护,推荐 ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 六、`disableScopecss: true` 的正确使用场景
|
||||||
|
|
||||||
|
这个选项不是为了"省事",它的合法用途很窄:
|
||||||
|
|
||||||
|
| 场景 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **调试排错** | 临时关闭隔离,确认是否是样式隔离导致的渲染问题 |
|
||||||
|
| **第三方 UI 库** | 子应用使用了不兼容 CSS 作用域的 UI 库(罕见) |
|
||||||
|
| **刻意演示** | 比如我们的 mock-app,故意制造冲突来教学 |
|
||||||
|
|
||||||
|
**生产环境应该永远保持 `disableScopecss: false`(默认值)。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 七、如果主应用的全局样式也过于宽泛怎么办?
|
||||||
|
|
||||||
|
前面的修复解决了"子应用样式泄漏到主应用/其他子应用"。反向的问题——**主应用的全局样式污染子应用**——同样需要处理。
|
||||||
|
|
||||||
|
**问题回顾(Q29):** 主应用的 `h2 { border-bottom: 2px solid purple !important }` 泄漏进了子应用。
|
||||||
|
|
||||||
|
**修复方案:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* ❌ 主应用当前写法 — 全局 h2 会影响所有 iframe: false 的子应用 */
|
||||||
|
h2 {
|
||||||
|
color: #667eea;
|
||||||
|
border-bottom: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ✅ 修复方案 1:给主应用的内容区加作用域 */
|
||||||
|
.app-main h2 {
|
||||||
|
color: #667eea;
|
||||||
|
border-bottom: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ✅ 修复方案 2:重置子应用容器内的样式 */
|
||||||
|
micro-app h2 {
|
||||||
|
border-bottom: none; /* 清除主应用的底边框 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ✅ 修复方案 3:主应用使用更具体的选择器 */
|
||||||
|
#main-app h2 { ... } /* ID 选择器,特异性更高 */
|
||||||
|
.main-content h2 { ... } /* 类选择器 + 标签,限定范围 */
|
||||||
|
```
|
||||||
|
|
||||||
|
**推荐:** 主应用的全局样式始终挂在容器选择器下(如 `.app-main h2`、`#main-app h2`),避免使用裸标签选择器(`h2`、`button`、`p`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 八、决策树:拿到冲突问题怎么修?
|
||||||
|
|
||||||
|
```
|
||||||
|
发现样式冲突
|
||||||
|
│
|
||||||
|
├── 子应用是 Vite 项目?
|
||||||
|
│ └── 是 → 设置 iframe: true(物理隔离 + ES Module 兼容)
|
||||||
|
│
|
||||||
|
├── 子应用是 Webpack / 普通 HTML?
|
||||||
|
│ └── 检查 disableScopecss 配置
|
||||||
|
│ ├── 是 true → 删掉它!(恢复默认隔离)← 90% 的情况
|
||||||
|
│ └── 是 false → 检查是否是主应用的全局样式泄漏
|
||||||
|
│ └── 主应用样式加 .app-main 等容器前缀
|
||||||
|
│
|
||||||
|
└── keepAlive 导致残留?
|
||||||
|
└── 修复了样式隔离后,keepAlive 不再是问题
|
||||||
|
(因为每个子应用的 CSS 有独立的作用域前缀)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 九、总结
|
||||||
|
|
||||||
|
| 问题 | 答案 |
|
||||||
|
|------|------|
|
||||||
|
| 修复成本高吗? | **极低。** 删掉一行 `disableScopecss: true` 即可 |
|
||||||
|
| 默认值就是安全的吗? | **是的。** `disableScopecss` 默认为 `false`,micro-app 自动隔离 |
|
||||||
|
| `disableScopecss: true` 什么时候用? | 调试排错时临时开启,或刻意演示冲突时 |
|
||||||
|
| 主应用的全局样式泄漏怎么办? | 全局样式加 `.app-main` 等容器前缀,不用裸标签选择器 |
|
||||||
|
| 为什么 Vite 项目不需要担心? | `iframe: true` 提供物理隔离,比 CSS 作用域更强 |
|
||||||
|
|
||||||
|
**一句话修复指南:不要设置 `disableScopecss: true`,Vite 项目必须 `iframe: true`,主应用全局样式挂容器选择器。三层到位,样式冲突清零。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 附录:接入新子应用检查清单
|
## 附录:接入新子应用检查清单
|
||||||
|
|
||||||
| 步骤 | 文件 | 操作 |
|
| 步骤 | 文件 | 操作 |
|
||||||
|
|||||||
@@ -30,17 +30,181 @@
|
|||||||
### Q2:micro-app 主应用和子应用之间是如何隔离的?
|
### Q2:micro-app 主应用和子应用之间是如何隔离的?
|
||||||
|
|
||||||
**答:**
|
**答:**
|
||||||
micro-app 提供**三层隔离**:
|
micro-app 提供**三层隔离体系**,各管各的维度,可以组合使用:
|
||||||
|
|
||||||
1. **样式隔离**(`disable-scopecss`,默认开启):
|
```
|
||||||
- 为子应用的每个样式规则自动添加 `micro-app[name=xxx]` 前缀,限制样式作用域
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ micro-app 隔离体系 │
|
||||||
|
│ │
|
||||||
|
│ ① CSS 样式隔离(默认开启,disableScopecss: false) │
|
||||||
|
│ └── 方式:CSS 前缀改写,不是 Shadow DOM! │
|
||||||
|
│ 子应用写了 h2 { color: red } │
|
||||||
|
│ micro-app 提取 CSS 后自动改成 │
|
||||||
|
│ micro-app[name=xxx] h2 { color: red } │
|
||||||
|
│ │
|
||||||
|
│ ② JS 沙箱(默认开启,disableSandbox: false) │
|
||||||
|
│ ├── with 沙箱:new Function() + Proxy(window) │
|
||||||
|
│ └── iframe 沙箱:浏览器原生 iframe 隔离 │
|
||||||
|
│ │
|
||||||
|
│ ③ 元素隔离(默认关闭,shadowDOM: false) │
|
||||||
|
│ └── 浏览器原生 Shadow DOM,DOM 树级别隔离 │
|
||||||
|
│ │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
2. **JS 沙箱隔离**(`disable-sandbox`,默认开启):
|
---
|
||||||
- **with 沙箱**:通过 `with(window.proxy)` + `new Function()` 执行子应用 JS,将子应用的全局变量操作代理到隔离的 microWindow 上
|
|
||||||
- **iframe 沙箱**:将子应用放入 iframe 中运行,利用浏览器原生的 iframe 隔离
|
|
||||||
|
|
||||||
3. **元素隔离**(`shadowDOM`,可选):
|
**一、CSS 样式隔离 — CSS 前缀改写(默认方案)**
|
||||||
- 将子应用放入 Shadow DOM,彻底隔离 DOM 树
|
|
||||||
|
**工作原理:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 子应用源码 */
|
||||||
|
h2 { color: orange; }
|
||||||
|
|
||||||
|
/* micro-app 自动改写为 ↓ */
|
||||||
|
micro-app[name=mock-app] h2 { color: orange; }
|
||||||
|
```
|
||||||
|
|
||||||
|
micro-app 在加载子应用时,会**提取所有 `<style>` 标签内容**,给每个 CSS 规则自动加上 `micro-app[name=xxx]` 属性选择器前缀,限制样式只作用于子应用容器内部。
|
||||||
|
|
||||||
|
**⚠️ 关键局限:只能阻止「子→主」,不能阻止「主→子」**
|
||||||
|
|
||||||
|
```
|
||||||
|
子应用样式泄漏到主应用(子→主):✅ CSS 前缀改写可以阻止
|
||||||
|
──────────────────────────────────────────────
|
||||||
|
子应用: h2 { color: orange }
|
||||||
|
→ 改写后: 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 内部 → 匹配 ❌
|
||||||
|
→ 需要额外手段::not(.child-app-wrapper) / iframe / shadowDOM
|
||||||
|
```
|
||||||
|
|
||||||
|
这就是我们在 `App.vue` 中遇到的问题 — 加了 `.app-main` 前缀还不够,因为子应用 DOM 就嵌在 `.app-main` 里面。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**二、JS 沙箱 — with 沙箱 vs iframe 沙箱**
|
||||||
|
|
||||||
|
| 维度 | with 沙箱 (`iframe: false`) | iframe 沙箱 (`iframe: true`) |
|
||||||
|
|------|-----------------------------|------------------------------|
|
||||||
|
| 原理 | `with(window.proxy)` + `new Function()` | 浏览器原生 iframe |
|
||||||
|
| 子应用 window | Proxy 代理的 microWindow | 真实的 iframe.contentWindow |
|
||||||
|
| ES Module | ❌ 不支持 `import`/`export` | ✅ 原生支持 |
|
||||||
|
| 适用 | Webpack UMD 子应用 | **Vite 子应用** |
|
||||||
|
| 性能 | 较快 | 略慢(多一层 iframe 渲染) |
|
||||||
|
| 共享内存 | ✅ 同一 document,可传引用 | ❌ 跨 document,必须 postMessage 序列化 |
|
||||||
|
|
||||||
|
**with 沙箱的执行过程:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// micro-app 内部简化逻辑
|
||||||
|
const code = await fetchJs() // 拉取子应用 JS 文本
|
||||||
|
|
||||||
|
const fn = new Function('window', `
|
||||||
|
with(window) {
|
||||||
|
${code} // 子应用代码拼接在这里
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
fn(microWindow) // microWindow = new Proxy(realWindow, handler)
|
||||||
|
```
|
||||||
|
|
||||||
|
子应用代码里的 `window.xxx` / `document.xxx` 等全局操作,都会被 `with(microWindow)` 劫持到 Proxy 代理上,实现对全局变量的拦截和隔离。
|
||||||
|
|
||||||
|
**为什么 Vite 子应用必须用 iframe?** Vite 开发环境输出 `<script type="module">` 格式的 ES Module,含 `import`/`export`。`new Function()` 无法执行模块语法(`import` 只能出现在模块静态顶层)。详见 Q8-1。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**三、元素隔离 — Shadow DOM(可选功能)**
|
||||||
|
|
||||||
|
**⚠️ 注意:Shadow DOM 不是默认的样式隔离方式!默认方式是 CSS 前缀改写。**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<micro-app name="mock-app" :shadowDOM="true" />
|
||||||
|
```
|
||||||
|
|
||||||
|
开启后,子应用的 DOM 被包裹在 Shadow Root 里:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<micro-app name="mock-app">
|
||||||
|
#shadow-root (open) ← Shadow DOM 边界
|
||||||
|
<style>h2 { color: orange; }</style>
|
||||||
|
<h2>子应用标题</h2> ← 样式天然隔离,内外互不穿透
|
||||||
|
</micro-app>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**四、三种隔离方案对比(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 格式 | `<script type="module">` ES Module | `<script>` IIFE / UMD bundle |
|
||||||
|
| 含 `import`/`export` | ✅ 是 | ❌ 否 |
|
||||||
|
| 文件数量 | 按需加载,可能几百个 | 一个 bundle(或少量 chunk) |
|
||||||
|
|
||||||
|
```
|
||||||
|
<!-- Vite 开发环境输出的 HTML -->
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
<!-- main.ts 内部还有上百个 import: -->
|
||||||
|
<!-- import { createApp } from 'vue' ← ES Module 语法 -->
|
||||||
|
<!-- import router from './router' ← ES Module 语法 -->
|
||||||
|
|
||||||
|
<!-- Webpack 开发环境输出的 HTML -->
|
||||||
|
<script src="/js/app.js"></script>
|
||||||
|
<!-- app.js 是一个巨大的 IIFE 包裹的 bundle: -->
|
||||||
|
<!-- (function(modules) { ... })({...}) ← 没有 import/export -->
|
||||||
|
```
|
||||||
|
|
||||||
|
**二、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` 必须出现在 `<script type="module">` 或 ES Module 文件的**静态顶层**,不能被 `new Function()` / `eval()` 动态执行。
|
||||||
|
|
||||||
|
**三、为什么 Webpack 可以不用 iframe?**
|
||||||
|
|
||||||
|
```
|
||||||
|
Webpack 构建流程:
|
||||||
|
import { ref } from 'vue' webpack打包 __webpack_require__('./vue')
|
||||||
|
import router from './router' ──────────→ __webpack_require__('./router')
|
||||||
|
↑ ES Module 语法 ↑ 普通函数调用
|
||||||
|
|
||||||
|
Vite 开发环境:
|
||||||
|
import { ref } from 'vue' 不打包! import { ref } from 'vue'
|
||||||
|
import router from './router' ──────────→ import router from './router'
|
||||||
|
↑ ES Module 语法 ↑ 仍然是 ES Module 语法
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Webpack** 在构建时就把 `import` 转成了 `__webpack_require__()` 这种普通函数调用,输出的是 IIFE 格式,`new Function()` 可以正常执行
|
||||||
|
- **Vite 开发模式** 不做打包,直接输出浏览器原生的 `import`/`export`,只能通过 iframe 的原生模块环境执行
|
||||||
|
|
||||||
|
**四、一张图总结**
|
||||||
|
|
||||||
|
```
|
||||||
|
子应用 JS 包含 import/export?
|
||||||
|
│
|
||||||
|
┌──────┴──────┐
|
||||||
|
│ │
|
||||||
|
否 是
|
||||||
|
(Webpack) (Vite)
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
new Function() iframe 沙箱
|
||||||
|
可直接执行 ✅ 唯一可行方案 ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
**五、面试追问:Vite 生产环境构建后还需要 iframe 吗?**
|
||||||
|
|
||||||
|
生产环境 Vite 用 Rollup 打包,输出的是普通 JS(无 `import`),理论上用 with 沙箱也能执行。但**仍然推荐 `iframe: true`**,原因:
|
||||||
|
|
||||||
|
1. **一致性**:开发/生产行为一致,避免"开发没问题,上线炸了"
|
||||||
|
2. **隔离性**:iframe 提供更彻底的样式/JS 隔离
|
||||||
|
3. **资源路径**:iframe 中的相对路径自动指向子应用 origin,不用额外处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 四、TypeScript 类型问题
|
## 四、TypeScript 类型问题
|
||||||
|
|
||||||
### Q9:micro-app 的 `lifeCycles` 回调中,为什么 `e.name` 会报 TS 类型错误?
|
### Q9:micro-app 的 `lifeCycles` 回调中,为什么 `e.name` 会报 TS 类型错误?
|
||||||
@@ -317,6 +577,100 @@ window.microApp?.dispatch({ type: '回复', payload: {} })
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Q12-1:`iframe: true` 模式下,通信的底层原理是什么?和 `iframe: false` 有什么不同?
|
||||||
|
|
||||||
|
**答:**
|
||||||
|
|
||||||
|
**API 层面完全一致** — 无论 `iframe: true` 还是 `iframe: false`,主应用和子应用都用同一套 API(`setData`、`getData`、`dispatch`、`addDataListener`)。框架在底层封装了通信差异。
|
||||||
|
|
||||||
|
**底层机制对比:**
|
||||||
|
|
||||||
|
```
|
||||||
|
iframe: false(with 沙箱) iframe: true(浏览器原生隔离)
|
||||||
|
┌──────────────────────┐ ┌──────────────────────────┐
|
||||||
|
│ 主应用 document │ │ 主应用 document │
|
||||||
|
│ window (共享) │ │ window │
|
||||||
|
│ ├── micro-app │ │ ├── micro-app │
|
||||||
|
│ │ └── 子应用 DOM │ │ │ └── <iframe> │
|
||||||
|
│ │ window ✅ 同一个│ │ │ └── 子应用 DOM │
|
||||||
|
│ │ 内存引用传递 │ │ │ window (独立) │
|
||||||
|
│ └── ... │ │ └── ... │
|
||||||
|
│ 直接读写 JS 对象 │ │ 跨域边界,必须序列化 │
|
||||||
|
└──────────────────────┘ └──────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**iframe 模式下的通信链路:**
|
||||||
|
|
||||||
|
```
|
||||||
|
主应用 iframe 子应用
|
||||||
|
│ │
|
||||||
|
│ microApp.setData('app', data) │
|
||||||
|
│ ↓ JSON.stringify(data) │
|
||||||
|
│ ↓ iframe.contentWindow │
|
||||||
|
│ .postMessage(data, '*') │
|
||||||
|
│ ───────── postMessage ────────→ │
|
||||||
|
│ │ window.addEventListener
|
||||||
|
│ │ ('message', handler)
|
||||||
|
│ │ ↓ JSON.parse
|
||||||
|
│ │ ↓ 触发 dataListener
|
||||||
|
│ │
|
||||||
|
│ window.parent │
|
||||||
|
│ ←──────── postMessage ──────── │
|
||||||
|
│ addEventListener('message') │ microApp.dispatch(msg)
|
||||||
|
│ ↓ JSON.parse │
|
||||||
|
│ ↓ 触发 dataListener │
|
||||||
|
```
|
||||||
|
|
||||||
|
**核心步骤:**
|
||||||
|
|
||||||
|
1. **主→子**:`microApp.setData()` → 框架 `JSON.stringify(data)` → `iframe.contentWindow.postMessage(serialized, '*')` → 子应用 `window.addEventListener('message')` → `JSON.parse` → 触发 `dataListener`
|
||||||
|
|
||||||
|
2. **子→主**:`window.microApp.dispatch(msg)` → 框架 `JSON.stringify(msg)` → `window.parent.postMessage(serialized, '*')` → 主应用 `window.addEventListener('message')` → `JSON.parse` → 触发 `dataListener`
|
||||||
|
|
||||||
|
**关键限制:postMessage 的"结构化克隆"**
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ 可以传: ❌ 不能传:
|
||||||
|
- string/number/boolean - 函数 / class
|
||||||
|
- 普通 Object / Array - DOM 节点 / Event
|
||||||
|
- Date / RegExp - Symbol
|
||||||
|
- Map / Set - WeakMap / WeakSet
|
||||||
|
- ArrayBuffer / Blob - Proxy 对象
|
||||||
|
```
|
||||||
|
|
||||||
|
这和 `iframe: false` 模式有本质区别 — with 沙箱下主应用和子应用共享同一个 `window`,可以传**内存引用**(对象改了,两边都能看到),而 iframe 模式走的是**序列化拷贝**(传完之后各是各的副本)。
|
||||||
|
|
||||||
|
**实战中的坑:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ❌ iframe 模式下这样传会丢失数据
|
||||||
|
microApp.setData('app', {
|
||||||
|
callback: () => {}, // 函数 → 序列化后丢失
|
||||||
|
element: document.body, // DOM 节点 → 序列化后丢失
|
||||||
|
})
|
||||||
|
|
||||||
|
// ✅ 正确做法:只传纯数据
|
||||||
|
microApp.setData('app', {
|
||||||
|
token: 'abc123',
|
||||||
|
userId: 42,
|
||||||
|
theme: 'dark',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**对比总结:**
|
||||||
|
|
||||||
|
| 维度 | `iframe: false` | `iframe: true` |
|
||||||
|
|------|-----------------|----------------|
|
||||||
|
| 通信机制 | 直接内存引用 | postMessage 序列化 |
|
||||||
|
| 传引用 | ✅ 对象共享 | ❌ 序列化拷贝 |
|
||||||
|
| 传函数 | ✅ 可以 | ❌ 丢失 |
|
||||||
|
| 性能 | 高(无序列化开销) | 中(JSON 序列化) |
|
||||||
|
| 数据大小 | 不受限 | 受 postMessage 限制 |
|
||||||
|
| API 一致性 | 完全相同 | 完全相同 |
|
||||||
|
| 调试 | 同 console | 需切换 iframe context |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Q13:`keep-alive` 保活机制的原理是什么?有什么注意事项?
|
### Q13:`keep-alive` 保活机制的原理是什么?有什么注意事项?
|
||||||
|
|
||||||
**答:**
|
**答:**
|
||||||
@@ -572,3 +926,159 @@ export default defineConfig({
|
|||||||
| 路由通配符(Vue Router 4) | `:page*`,不是 `*` |
|
| 路由通配符(Vue Router 4) | `:page*`,不是 `*` |
|
||||||
| lifeCycles 回调参数 | `(e: CustomEvent, appName: string)` |
|
| lifeCycles 回调参数 | `(e: CustomEvent, appName: string)` |
|
||||||
| 子应用路由 base | `window.__MICRO_APP_BASE_ROUTE__ \|\| '/'` |
|
| 子应用路由 base | `window.__MICRO_APP_BASE_ROUTE__ \|\| '/'` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、样式穿透实战
|
||||||
|
|
||||||
|
### Q21:子应用 `iframe: false` 模式下,主应用的全局样式为什么会穿透到子应用?如何彻底解决?
|
||||||
|
|
||||||
|
**答:**
|
||||||
|
|
||||||
|
这是微前端 `iframe: false` 模式下最常见的样式泄漏问题。即使使用了 `.app-main h2` 这样的"作用域选择器",样式仍然会穿透。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**一、问题现象**
|
||||||
|
|
||||||
|
在 `http://localhost:8080/mock-app` 页面中,子应用的 `<h2>` 元素出现了主应用的紫色 `border-bottom` 样式,而不是子应用自己定义的样式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**二、根因分析:DOM 层级重叠**
|
||||||
|
|
||||||
|
主应用的 `App.vue` 布局结构:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- App.vue 模板 -->
|
||||||
|
<div id="main-app">
|
||||||
|
<header class="app-header">...</header>
|
||||||
|
<main class="app-main">
|
||||||
|
<router-view /> <!-- ← 这里渲染所有页面 -->
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
当访问 `/mock-app` 时,`<router-view>` 渲染的是 `ChildApp.vue`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- ChildApp.vue 模板 -->
|
||||||
|
<div class="child-app-wrapper">
|
||||||
|
<micro-app name="mock-app" :iframe="false">
|
||||||
|
<!-- 子应用的完整 DOM 在这里!包括 <h2>、<button>、<p> 等 -->
|
||||||
|
</micro-app>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**最终 DOM 树:**
|
||||||
|
|
||||||
|
```
|
||||||
|
main.app-main ← 主应用的样式容器
|
||||||
|
├── div.home-page ← 访问 /home 时
|
||||||
|
│ └── h2 (主应用的 h2)
|
||||||
|
│
|
||||||
|
└── div.child-app-wrapper ← 访问 /mock-app 时
|
||||||
|
└── <micro-app name="mock-app">
|
||||||
|
└── h2 (子应用的 h2) ← ⚠️ 也在 .app-main 内部!
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键点**:子应用的 DOM 被渲染在 `.app-main` 内部,所以 `.app-main h2` 这个**后代选择器**会匹配到子应用的 `<h2>`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**三、常见的错误认知**
|
||||||
|
|
||||||
|
很多开发者以为加上父级 class 就能隔离:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* ❌ 错误:这样写仍然会穿透! */
|
||||||
|
.app-main h2 {
|
||||||
|
border-bottom: 2px solid #667eea !important;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**为什么不行?** 因为 `.app-main h2` 是**后代选择器**(任意层级),它会匹配 `.app-main` 下面所有层级的 `h2`,包括嵌套在 `micro-app` 内部的子应用 `h2`。
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* ❌ 更差:这样写 100% 泄漏 */
|
||||||
|
h2 {
|
||||||
|
border-bottom: 2px solid purple;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**四、正确解法**
|
||||||
|
|
||||||
|
**方案 1:子选择器 + :not() 排除子应用容器(本项目采用)**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* ✅ 正确:只匹配主应用页面内的 h2,排除子应用容器 */
|
||||||
|
.app-main > :not(.child-app-wrapper) h2 {
|
||||||
|
border-bottom: 2px solid #667eea !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main > :not(.child-app-wrapper) button { ... }
|
||||||
|
.app-main > :not(.child-app-wrapper) p { ... }
|
||||||
|
.app-main > :not(.child-app-wrapper) table { ... }
|
||||||
|
.app-main > :not(.child-app-wrapper) code { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**原理:**
|
||||||
|
- `>` 子选择器:只匹配 `.app-main` 的**直接子元素**
|
||||||
|
- `:not(.child-app-wrapper)`:排除类名为 `.child-app-wrapper` 的直接子元素
|
||||||
|
- 子应用的容器 `.child-app-wrapper` 被排除 → 其内部所有元素都不受主样式影响
|
||||||
|
|
||||||
|
```
|
||||||
|
.app-main
|
||||||
|
├── div.home-page ← :not(.child-app-wrapper) → ✅ 样式生效
|
||||||
|
└── div.child-app-wrapper ← 被 :not() 排除 → ❌ 样式不生效
|
||||||
|
└── h2 (安全!)
|
||||||
|
```
|
||||||
|
|
||||||
|
**方案 2:开启 iframe 沙箱(最彻底)**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: 'mock-app',
|
||||||
|
iframe: true, // ← 浏览器原生隔离,样式 100% 不互通
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:彻底的样式/JS 隔离
|
||||||
|
**缺点**:性能略差、调试不便、通信成本增加
|
||||||
|
|
||||||
|
**方案 3:使用 Shadow DOM(激进方案)**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<micro-app name="mock-app" :shadowDOM="true" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**缺点**:Shadow DOM 边界会阻断很多 CSS 特性(如全局字体),兼容性成本高。
|
||||||
|
|
||||||
|
**方案 4:CSS @scope(未来方案)**
|
||||||
|
|
||||||
|
```css
|
||||||
|
@scope (.app-main) to (micro-app) {
|
||||||
|
h2 { border-bottom: 2px solid #667eea; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`@scope` 的 `to` 边界可以精确控制样式不穿透到 `micro-app` 内部。Chrome 118+、Safari 17.4+ 已支持,但目前还不够普及。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**五、各方案对比**
|
||||||
|
|
||||||
|
| 方案 | 隔离程度 | 性能 | 复杂度 | 适用场景 |
|
||||||
|
|------|----------|------|--------|----------|
|
||||||
|
| `:not(.child-app-wrapper)` | 中(单向阻断) | 无影响 | 低 | 已知子应用容器结构的场景 |
|
||||||
|
| iframe 沙箱 | 高(完全隔离) | 略低 | 低(配置即可) | Vite 子应用、高隔离要求 |
|
||||||
|
| Shadow DOM | 高(DOM 隔离) | 中 | 高(兼容问题多) | 对隔离有极致要求的场景 |
|
||||||
|
| CSS @scope | 中高 | 无影响 | 低 | 未来主流方案(等浏览器普及) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**六、核心教训**
|
||||||
|
|
||||||
|
> 在 `iframe: false` 模式下,子应用和主应用共享同一个 document,**任何后代选择器都会穿透到子应用内部**。给选择器加父级 class(如 `.app-main`)只在父级和子应用不在同一容器时才有效。真正有效的做法是用**子选择器**限制层级深度,同时用 `:not()` 排除子应用容器。
|
||||||
|
|||||||
39
src/App.vue
39
src/App.vue
@@ -48,11 +48,30 @@ html, body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 冲突演示:主应用全局样式 ---------- */
|
/* ---------- 主应用内容区样式(限定在主应用自身页面内) ---------- */
|
||||||
/* 这些通用选择器如果被子应用的同名选择器覆盖,就会发生冲突 */
|
/*
|
||||||
|
问题:子应用在 iframe: false 模式下,DOM 直接嵌入主文档,
|
||||||
|
挂在 <router-view> → <ChildApp.vue> → <micro-app> 下面,
|
||||||
|
而 <router-view> 又在 .app-main 内部。
|
||||||
|
所以 .app-main h2 这种「后代选择器」会穿透到子应用的 h2。
|
||||||
|
|
||||||
|
之前(会泄漏): h2 { ... } ← 没有作用域,100% 泄漏
|
||||||
|
中间(仍泄漏): .app-main h2 { ... } ← 子应用也在 .app-main 里!
|
||||||
|
现在(已修复): .app-main > :not(.child-app-wrapper) h2 { ... }
|
||||||
|
↑ 用子选择器 > 只匹配 .app-main 的直接子元素,
|
||||||
|
再用 :not(.child-app-wrapper) 排除子应用容器,
|
||||||
|
彻底阻断样式进入 micro-app 内部
|
||||||
|
|
||||||
|
DOM 层级示意:
|
||||||
|
.app-main
|
||||||
|
├── div.home-page ← :not(.child-app-wrapper) → ✅ 样式生效
|
||||||
|
└── div.child-app-wrapper ← 被 :not() 排除 → ❌ 样式不生效
|
||||||
|
└── <micro-app>
|
||||||
|
└── 子应用 h2/button/p... ← 安全,不受主应用样式影响
|
||||||
|
*/
|
||||||
|
|
||||||
/* 冲突点 ①:h2 — 主应用想要紫色渐变标题 */
|
/* 冲突点 ①:h2 — 主应用想要紫色渐变标题 */
|
||||||
h2 {
|
.app-main > :not(.child-app-wrapper) h2 {
|
||||||
font-family: 'Microsoft YaHei', sans-serif !important;
|
font-family: 'Microsoft YaHei', sans-serif !important;
|
||||||
font-size: 20px !important;
|
font-size: 20px !important;
|
||||||
color: #667eea !important;
|
color: #667eea !important;
|
||||||
@@ -65,7 +84,7 @@ h2 {
|
|||||||
|
|
||||||
|
|
||||||
/* 冲突点 ②:button — 主应用想要紫色直角按钮 */
|
/* 冲突点 ②:button — 主应用想要紫色直角按钮 */
|
||||||
button {
|
.app-main > :not(.child-app-wrapper) button {
|
||||||
background: #667eea !important;
|
background: #667eea !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
@@ -76,13 +95,13 @@ button {
|
|||||||
cursor: pointer !important;
|
cursor: pointer !important;
|
||||||
letter-spacing: 0 !important;
|
letter-spacing: 0 !important;
|
||||||
}
|
}
|
||||||
button:hover {
|
.app-main > :not(.child-app-wrapper) button:hover {
|
||||||
background: #5a6fd6 !important;
|
background: #5a6fd6 !important;
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 冲突点 ③:p 段落 — 主应用想要紧凑排版 */
|
/* 冲突点 ③:p 段落 — 主应用想要紧凑排版 */
|
||||||
p {
|
.app-main > :not(.child-app-wrapper) p {
|
||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
line-height: 1.6 !important;
|
line-height: 1.6 !important;
|
||||||
color: #444 !important;
|
color: #444 !important;
|
||||||
@@ -90,26 +109,26 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 冲突点 ④:table — 主应用想要紫色无边框表格 */
|
/* 冲突点 ④:table — 主应用想要紫色无边框表格 */
|
||||||
table {
|
.app-main > :not(.child-app-wrapper) table {
|
||||||
border-collapse: collapse !important;
|
border-collapse: collapse !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
th {
|
.app-main > :not(.child-app-wrapper) th {
|
||||||
background: #667eea !important;
|
background: #667eea !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
padding: 8px !important;
|
padding: 8px !important;
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
font-size: 13px !important;
|
font-size: 13px !important;
|
||||||
}
|
}
|
||||||
td {
|
.app-main > :not(.child-app-wrapper) td {
|
||||||
padding: 6px 8px !important;
|
padding: 6px 8px !important;
|
||||||
border-bottom: 1px solid #e0e0e0 !important;
|
border-bottom: 1px solid #e0e0e0 !important;
|
||||||
font-size: 13px !important;
|
font-size: 13px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 冲突点 ⑤:code 标签 — 主应用想要紫色代码块 */
|
/* 冲突点 ⑤:code 标签 — 主应用想要紫色代码块 */
|
||||||
code {
|
.app-main > :not(.child-app-wrapper) code {
|
||||||
background: #e8e0f0 !important;
|
background: #e8e0f0 !important;
|
||||||
color: #5e35b1 !important;
|
color: #5e35b1 !important;
|
||||||
border: 1px solid #b39ddb !important;
|
border: 1px solid #b39ddb !important;
|
||||||
|
|||||||
@@ -54,13 +54,13 @@ export const subApps: SubAppConfig[] = [
|
|||||||
url: 'http://localhost:5174/',
|
url: 'http://localhost:5174/',
|
||||||
baseroute: '/mock-app',
|
baseroute: '/mock-app',
|
||||||
// ============================================
|
// ============================================
|
||||||
// ⚠️ 样式冲突演示专用:
|
// ✅ iframe: true — 浏览器原生隔离,样式/JS 完全不互通
|
||||||
// iframe: false → 子应用 DOM 直接嵌入主文档
|
// 之前(有样式穿透): iframe: false
|
||||||
// disableScopecss: true → 关闭样式隔离,双方样式互相泄漏
|
// → 子应用 DOM 嵌入主文档 → 主应用的 .app-main h2 泄漏进去
|
||||||
// 这是一个纯 HTML 页面(无 JS 框架),不需要 ES Module 支持
|
// 现在(已修复): iframe: true
|
||||||
|
// → 子应用在独立 iframe 中运行 → 完全隔离 ✅
|
||||||
// ============================================
|
// ============================================
|
||||||
iframe: false,
|
iframe: true,
|
||||||
disableScopecss: true,
|
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
routerMode: 'native'
|
routerMode: 'native'
|
||||||
},
|
},
|
||||||
@@ -68,13 +68,8 @@ export const subApps: SubAppConfig[] = [
|
|||||||
name: 'mock-app-2',
|
name: 'mock-app-2',
|
||||||
url: 'http://localhost:5175/',
|
url: 'http://localhost:5175/',
|
||||||
baseroute: '/mock-app-2',
|
baseroute: '/mock-app-2',
|
||||||
// ============================================
|
// ✅ 同上:使用默认样式隔离
|
||||||
// ⚠️ 子应用间样式冲突演示:
|
|
||||||
// 与 mock-app 相同:iframe: false, disableScopecss: true
|
|
||||||
// 用于验证:前一个子应用的样式是否会影响后一个子应用
|
|
||||||
// ============================================
|
|
||||||
iframe: false,
|
iframe: false,
|
||||||
disableScopecss: true,
|
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
routerMode: 'native'
|
routerMode: 'native'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user