iframe作为微前端方案的几个问题

iframe提供了一个原生的window沙箱,内部有完整的history和location接口,路由也彻底与主应用解耦。在微前端的实现中,天然能被用来隔离子应用。但iframe也存在以下几个问题:

  1. 路由状态丢失
  2. DOM割裂严重,弹窗只能在iframe内部展示
  3. iframe web应用之间通信困难
  4. iframe加载SPA应用白屏时间过长

下面,我们来逐个认识下他们,并看看有哪些办法来解决。

路由状态丢失

当浏览器刷新页面时,iframe的当前路由状态会丢失。比如,你在页面中使用了一个 <iframe>,iframe 内部加载了某个子页面。这个 iframe 的 URL 有一段路由路径:

js 复制代码
http://your-domain.com/container
       └── iframe src="http://child-app.com/#/dashboard"

你在 iframe 内部导航到了一个profile页面,路由状态变成了

js 复制代码
http://your-domain.com/container
       └── iframe src="http://child-app.com/#/profile"

这时候你刷新整个浏览器页面(即父页面),iframe 又重新加载了它最初的 src URL,原来的路由状态(比如 /profile)会丢失 ,iframe的路由状态又回到了http://child-app.com/#/dashboard

为什么路由状态会丢失?

iframe 加载的是一个子页面,它自己内部维护自己的路由状态。当你刷新父页面时,整个 DOM 被重建 ,包括 iframe 标签本身。所以它的 src 会重新被设置成你原先的值(比如 http://child-app.com/#/dashboard),不会记得你用户之前在 iframe 里导航到了 /profile

解决办法

把 iframe 的路由状态同步到父应用的 URL,在iframe加载时同步回iframe。

让父页面记录 iframe 的状态,比如:

bash 复制代码
http://your-domain.com/container?childRoute=/profile

然后在加载 iframe 时拼接这个路由:

html 复制代码
<iframe src={`http://child-app.com/#${childRoute}`}></iframe>
  • 你可能会问,iframe的路由状态怎么同步到父应用的URL上呢?

    我们可以重写掉iframe window上的pushState/replaceState方法,以及监听hashchange/popstate事件,把iframe的路由状态作为参数写到主应用的URL上。伪代码如下:

js 复制代码
 const iframeWindow = iframe.contentWindow;
 const history = iframeWindow.history;
 history.pushState = function (data, title, url: string) {
  syncUrlToWindow(iframeWindow);  // syncUrlToWindow 负责把iframe路由写到主应用的URL上
 }
 history.replaceState = function (data, title, url: string) {
  syncUrlToWindow(iframeWindow);
 }
 iframeWindow.addEventListener("hashchange", () => syncUrlToWindow(iframeWindow));
 iframeWindow.addEventListener("popstate", () => {
    syncUrlToWindow(iframeWindow);
  });

DOM割裂严重,弹窗只能在iframe内部展示

每个iframe或子应用有自己独立的window,document,CSS样式表,JS作用域。当你在 iframe 内部调用代码,比如:

js 复制代码
document.body.appendChild(modalElement);

弹窗只会出现在iframe内部,无法穿透iframe 显示在整个主页面(父页面)上方。你想要一个弹窗遮罩整个页面(全局),结果它只遮住了小小的 iframe 区域。

为什么无法覆盖?

iframe 是一个完整的独立页面(document) ,和父页面是两个完全分离的 DOM 世界。弹窗的定位和层级(如 position: fixed、z-index)只能作用在当前 document,即iframe的document上。

解决办法

弹窗放在主应用,由子应用触发。子应用通过 postMessage 或全局事件总线发出事件或消息,主应用监听并渲染弹窗,弹窗 DOM 节点实际挂在主应用 DOM 上

js 复制代码
// 子应用发消息
window.parent.postMessage({ type: 'OPEN_MODAL', data: {...} }, '*');
js 复制代码
// 主应用监听
window.addEventListener('message', (event) => {
  if (event.data.type === 'OPEN_MODAL') {
    showGlobalModal(event.data.data);
  }
});

iframe web应用之间通信困难

浏览器出于安全目的实施了同源策略,同源是指协议、域名、端口都相同。当iframe的协议、域名和端口与主页面都相同时,我们把它叫做同源iframe,否则,叫做跨越iframe。

同源策略给iframe与主页面之间的通信带来了困难,尤其是跨域iframe。主页面无法直接访问跨域iframe页面的 DOM、JS 对象等,会抛出安全错误。

跨域iframe与主页面的通信

虽然困难,但不是不可能。我们可以使用HTML5提供的官方跨域通信机制postMessage API。

postMessage API

由主页面调用postMessage发出消息:

js 复制代码
iframe.contentWindow.postMessage('hello', 'https://iframe-domain.com');

被嵌入的 iframe 页面监听消息:

js 复制代码
window.addEventListener('message', (event) => {
  // 安全校验
  if (event.origin === 'https://your-main-domain.com') {
    console.log(event.data);
  }
});

同源iframe与主页面的通信

主页面和同源iframe之间的通信是比较简单的,可以直接通过 JavaScript 访问彼此的对象和方法,没有跨域限制。

主页面访问iframe

html 复制代码
<!-- 主页面 -->
<iframe id="myIframe" src="/child.html"></iframe>

主页面 JavaScript:

ini 复制代码
const iframe = document.getElementById('myIframe');

// 调用 iframe 中暴露的方法
iframe.onload = () => {
  iframe.contentWindow.sayHelloFromParent && iframe.contentWindow.sayHelloFromParent();
};

iframe 页面(/child.html):

html 复制代码
<script>
  // 被主页面调用的方法
  function sayHelloFromParent() {
    alert('Hello from parent!');
  }
</script>

iframe调用主页面方法(向主页面发送消息) 主页面定义方法:

html 复制代码
<script>
  window.sayHelloFromIframe = function () {
    alert('Hello from iframe!');
  };
</script>

iframe 页面中访问:

js 复制代码
// iframe 中的 JS
window.parent.sayHelloFromIframe && window.parent.sayHelloFromIframe();

另外,BroadcastChannel API 是一个浏览器提供的用于同源文档之间通信 的原生 Web API。它可以在不同的浏览器上下文环境之间进行消息广播 。它也能解决主页面与同源iframe之间的通信,具体请查阅Broadcast Channel API。

iframe加载SPA应用白屏时间过长

这个问题常见于基于 iframe 的微前端架构或嵌入式系统中。对于 SPA(Single Page Application)应用来说,iframe 每次加载都重新初始化一整个应用,导致白屏时间长、用户体验差。

下面是一些常见的优化方案,可以有效减少 iframe 白屏时间:

预加载 iframe

提前创建隐藏的 iframe 并加载目标页面,等用户点击或需要显示时再展示。示例:

ini 复制代码
<iframe id="app-frame" src="about:blank" style="display: none;"></iframe>

<script>
  const iframe = document.getElementById("app-frame");
  iframe.src = "https://your-spa-app.com"; // 提前加载
  iframe.onload = () => {
    // 等加载完成后再展示
    iframe.style.display = "block";
  };
</script>

使用 Loading Skeleton / 占位动画

在 iframe 加载时展示骨架屏或 loading 动画,减少"白屏感知"。我们可以分两步来实现:

  • iframe 容器层级内加一层 loading 遮罩层;
  • 监听 iframe.onload 后移除遮罩。

使用 keep-alive iframe 或 sandbox 缓存技术

如果使用微前端框架(如 Qiankun、Wujie),可以启用 子应用保活 机制:

  • 子应用第一次加载后,把frame相关的DOM保持在内存中;
  • 后续切换时直接切回 DOM,不重新初始化。

总结

我们看完了iframe常见的4个问题,和他们的解决办法。如果感兴趣并想深入了解的,可以学习下Wujie这个微前端开源仓库,因为它采用了iframe来实现微前端的沙箱功能。看看它是如何来解决上述几个问题的。

相关推荐
江华森22 分钟前
Web漏洞与中间件框架漏洞实战详解
面试
lovingsoft25 分钟前
Chrome Lighthouse 详解
前端·chrome
Qlly35 分钟前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝39 分钟前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦40 分钟前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡1 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI1 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95271 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人1 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring
内存过载1 小时前
用表格模拟甘特图:项目排期的土办法
前端·低代码