引言
沙箱隔离(Sandbox Isolation)是微前端架构中的核心技术,用于确保多个子应用在同一页面中运行时,资源(JS/CSS/环境变量)相互隔离,避免冲突。其核心目标是让每个子应用像独立运行一样,互不干扰。
为什么需要沙箱隔离?
在微前端场景中,多个子应用共享同一个浏览器环境,可能引发以下问题:
- 全局变量污染:子应用修改
window对象导致其他应用异常。 - 样式冲突:不同子应用的 CSS 类名或选择器相互覆盖。
- 事件/定时器泄露:子应用卸载后未清理监听器或定时器,导致内存泄漏。
- 路由冲突:多个应用同时操作
history或location。
沙箱隔离的实现方式
1. JavaScript 隔离
-
核心思想:为每个子应用创建独立的全局变量环境。
-
实现方案:
-
快照沙箱(Snapshot Sandbox) :
在子应用加载前保存全局状态(如window属性),卸载时恢复。
适用场景:单实例子应用(同一时间只运行一个子应用)。
代码示例:class SnapshotSandbox { constructor() { this.modifyProps = {}; // 记录子应用修改的全局变量 this.windowSnapshot = {}; // 保存初始 window 快照 } activate() { // 1. 保存当前 window 状态 this.windowSnapshot = {}; for (const prop in window) { this.windowSnapshot[prop] = window[prop]; } // 2. 恢复子应用之前修改的全局变量 Object.keys(this.modifyProps).forEach(prop => { window[prop] = this.modifyProps[prop]; }); } deactivate() { // 1. 记录子应用修改了哪些全局变量 for (const prop in window) { if (window[prop] !== this.windowSnapshot[prop]) { this.modifyProps[prop] = window[prop]; // 2. 还原初始 window 状态 window[prop] = this.windowSnapshot[prop]; } } } } -
代理沙箱(Proxy Sandbox) :
使用Proxy为每个子应用创建一个虚拟的window代理对象,子应用的操作不会污染真实window。
适用场景:多实例子应用(多个子应用同时运行)。
代码示例:class ProxySandbox { constructor() { const fakeWindow = {}; this.proxy = new Proxy(fakeWindow, { get(target, prop) { return prop in target ? target[prop] : window[prop]; }, set(target, prop, value) { target[prop] = value; // 修改仅作用于 fakeWindow return true; }, }); } } // 子应用运行时使用 proxy 代替真实 window const sandbox = new ProxySandbox(); (function(window) { window.a = 1; // 操作的是 sandbox.proxy })(sandbox.proxy);
fakeWindow的核心作用(1) 隔离存储:子应用的私有全局变量池
-
物理隔离:所有通过代理对象 (
this.proxy) 设置的属性(如window.a = 1),实际存储在fakeWindow对象中,而非真实window。 -
示例验证:
console.log(fakeWindow.a); // 输出 1(子应用的修改被隔离在此) console.log(window.a); // 输出 undefined(真实全局环境未被污染)(2) 透明访问:共享主应用的全局属性
- 读操作穿透:当子应用访问全局属性(如
window.location),若fakeWindow不存在该属性,则代理会从真实window读取。
// 子应用代码 console.log(window.location.href); // 实际返回真实 window.location沙箱运行流程
(1) 写入操作
-
代码示例:
window.a = 1 -
执行路径:
- 代理拦截
set操作。 - 将属性
a写入fakeWindow。 - 真实
window不受影响。
(2) 读取操作
-
代码示例:
console.log(window.a) -
执行路径:
- 代理拦截
get操作。 - 检查
fakeWindow是否存在属性a。 - 存在则返回
fakeWindow.a,否则返回真实window.a。
-
4. 设计意义
| 场景 | 无 fakeWindow | 有 fakeWindow |
|---|---|---|
| 子应用设置全局变量 | 直接污染真实 window | 变量存储在 fakeWindow,隔离生效 |
| 子应用读取全局变量 | 直接访问真实 window | 优先读 fakeWindow,未命中则穿透读真实 window |
| 多实例共存 | 变量冲突导致相互覆盖 |
2. CSS 隔离
-
核心思想:避免子应用的样式影响全局或其他应用。
-
实现方案:
-
命名空间(Namespace) :
为子应用的 CSS 添加唯一前缀(如app1-button)。
工具支持:Webpack 的css-loader配置localIdentName。/* 编译前 */ .button { color: red; } /* 编译后 */ .app1-button-xyz123 { color: red; } -
Shadow DOM:
利用浏览器原生的 DOM 隔离机制,子应用的样式仅在其 Shadow Tree 内生效。
代码示例:const shadowRoot = element.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ` <style> .button { color: red; } /* 仅在此 Shadow DOM 内生效 */ </style> <button class="button">Click</button> `; -
动态卸载/加载样式表:
子应用加载时插入 CSS,卸载时移除。
缺点:无法彻底解决同名类冲突。
-
3. 其他资源隔离
- 事件监听:在子应用卸载时自动移除其绑定的全局事件(如
resize)。 - 定时器清理:跟踪子应用创建的
setTimeout/setInterval,卸载时清除。 - 路由同步:主应用统一管理路由变化,避免子应用直接操作
history。
主流框架的沙箱实现
| 框架 | JS 隔离方案 | CSS 隔离方案 | 特点 |
|---|---|---|---|
| Qiankun | Proxy 沙箱(多实例) | 动态样式表加载/卸载 | 支持多实例,隔离彻底 |
| Garfish | Proxy 沙箱 | CSS 前缀/Shadow DOM | 轻量级,支持依赖共享 |
| MicroApp | 基于 Web Components | Shadow DOM | 浏览器原生隔离,性能较好 |
沙箱隔离的局限性
- 性能损耗:Proxy 和 CSS 动态处理可能增加运行时开销。
- 浏览器 API 限制:某些 API(如
localStorage)无法完全隔离。 - 第三方库兼容性:部分库(如直接操作 DOM 的 jQuery)可能绕过沙箱。
总结
沙箱隔离是微前端的基石,通过虚拟环境和资源管控确保子应用独立运行。实际项目中需根据场景选择隔离方案,并注意性能与兼容性平衡。