引言
沙箱隔离(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)可能绕过沙箱。
总结
沙箱隔离是微前端的基石,通过虚拟环境和资源管控确保子应用独立运行。实际项目中需根据场景选择隔离方案,并注意性能与兼容性平衡。
原文链接:https://juejin.cn/post/7474264487497793587