去年公司启动门户改造,旧的 OA 门户是七八年前的 Vue 2 单体应用,动一处牵一片,团队不愿意在老代码上继续堆功能。新的业务表单和报表页面改用低代码平台搭建,产出的页面是独立部署的 Web 应用,最终要求是:用户在老门户里点菜单,无缝打开这些新页面,看起来像一个系统。这个"嵌"字,前前后后折腾了将近一个月,iframe 和微前端两条路都走了一遍,这里把踩过的坑和最终的取舍逻辑完整复盘一遍。
一、背景:老门户里嵌低代码页面是个什么需求
1.1 项目现状
公司门户的处境很典型:老门户承担统一登录、组织架构、消息推送这些底座能力,业务部门的新需求却越来越碎------审批流、点检表、设备台账、月度报表,两周一个样。低代码平台搭这类页面效率确实高,搭建出来的页面以独立前端应用的形式部署,有自己的路由和构建产物。于是问题变成了:两个技术栈不同、部署节奏不同的前端应用,如何在同一个浏览器窗口里共存,还要让用户感知不到割裂。
1.2 为什么嵌入是个技术活
嵌入本身不难,一个标签就能把页面引进来。难的是三件事:一是登录态怎么打通,总不能让用户在新页面里再登一次;二是页面之间怎么通信,门户的头像、待办数字要跟着子页面操作实时变;三是视觉和交互体验,弹窗、路由、样式都要像同一个系统。这三件事里任何一件处理不好,用户第一反应就是"这是两个系统拼起来的",项目就算失败一半。
二、iframe 方案:最快落地,代价在后头
2.1 三天就能跑通的初版
第一版方案没什么悬念,直接 iframe。老门户的路由不动,菜单点击时把 iframe 的 src 指向低代码页面的地址,容器铺满内容区。iframe 天然的浏览上下文隔离,意味着子页面的全局变量、路由、样式表全都被锁在框里,老门户的 Element UI 样式不会污染子页面,子页面的组件库也不会反过来改门户的样式。前三天一切顺利,演示的时候领导都觉得"这不就成了吗"。
2.2 弹窗被裁剪:第一个翻车点
真正的麻烦从细节开始。低代码页面里的表单弹窗、日期选择器,默认渲染在 iframe 文档流内,而 iframe 容器是我们自己算的高度,内容一溢出就被裁掉。日期控件往下弹没问题,往上弹直接截半,用户点不到上个月的日期。试过给容器动态撑高,结果页面出现双滚动条,滚轮事件在父子页面之间来回切换,体验割裂。后来又遇到弹窗需要覆盖整个屏幕的场景------iframe 里的元素再怎么设 z-index,也出不了那方框。这类问题的根因都一样:iframe 的布局边界是物理隔离的,子页面无法感知自己在父页面里的位置。
2.3 登录态打通折腾了一周
登录态是另一场持久战。老门户用 Cookie 存会话,低代码页面部署在另一个域名下,浏览器 SameSite 策略默认不让 Cookie 跨站携带,子页面首次加载直接跳登录页。改成 SameSite=None 加 Secure 前提是全站 HTTPS,内网环境证书又是一顿折腾。跨域 Cookie 行为在各浏览器上还有差异,同一个配置在两台电脑上表现不一致,排查了整整一周才稳定下来。这段经历让团队第一次认真评估:iframe 的隔离是双刃剑,它隔离了样式,也隔离了一切本该共享的东西。
2.4 性能与体验的账也要算
稳定之后又暴露出性能短板。iframe 每次切换菜单都是一次完整的页面加载,低代码页面的组件库、图表库全部重新拉取,内网带宽不宽时首屏要等两三秒,白屏期间用户只能干瞪眼。资源缓存策略可以缓解,但 HTML 文档本身不走长缓存,版本更新后资源碎片化的问题依然存在。再加上无法预加载、无法共享主应用已下载的依赖,iframe 方案在页面数量增多后,体验和流量两头的账都越来越难看。
三、微前端方案:qiankun 的解法
3.1 注册子应用
第二版换 qiankun。它的思路是运行时接管子应用的 JS 和样式资源,把子应用"溶解"进主应用的页面里,没有 iframe 那层物理边界。主应用侧的核心代码其实不长:
javascript
import { registerMicroApps, start } from 'qiankun';
// 主应用注册低代码页面作为子应用
registerMicroApps([
{
name: 'lowcode-app',
entry: '//lc.internal.example.com', // 低代码平台产出的应用入口
container: '#subapp-container', // 挂载节点
activeRule: '/lowcode', // 命中该路由时加载子应用
props: { token: getSessionToken(), user: currentUser }
}
], {
// 生命周期兜底:子应用加载失败时给出可感知的提示
{
beforeLoad: [(app) => { console.log(`[main] ${app.name} beforeLoad`); return Promise.resolve(); }],
afterMount: [(app) => { hideSubappLoading(app.name); return Promise.resolve(); }]
}
});
start({ prefetch: 'all' }); // 空闲时预取子应用资源,二次进入秒开
props 里把主应用的会话令牌和用户信息直接传下去,子应用在 mount 生命周期里接住,登录态问题在框架层面就解决了大半,之前折腾一周的跨域 Cookie 直接绕开。
3.2 样式隔离与 JS 沙箱
iframe 的隔离是免费的,qiankun 的隔离是配置出来的。样式隔离官方提供 experimentalStyleIsolation(运行时给子应用选择器加 scope 前缀)和严格的 shadow DOM 两种模式,弹窗这类挂在 body 下的组件要选对模式才不会被漏掉;JS 侧默认开启沙箱,子应用对 window 的写操作会被代理拦截,卸载时自动还原。关键配置如下:
javascript
// 主应用 start 时的隔离配置
start({
sandbox: {
strictStyleIsolation: false, // shadow DOM 太严格,弹窗组件样式取不到
experimentalStyleIsolation: true // 改用 scope 前缀方案,兼容性更好
}
});
// 子应用 webpack 配置,让 qiankun 能拿到资源依赖
export default {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`
}
};
这里踩过一个小坑:一开始开了 shadow DOM 严格隔离,低代码页面里挂在 document.body 上的全局弹窗样式全部失效,白屏弹窗排查了半天,最后换回 scope 前缀方案才正常。隔离强度和兼容性之间要按子应用的实际行为折中。
3.3 公共依赖:省流量也省心智
微前端还有一个隐性收益是公共依赖复用。主应用已经加载了 Vue 全家桶和组件库,通过 externals 约定,子应用构建时把这些包排除掉,运行时直接用主应用实例。门户首页的包体没变大,子应用的首屏反而因为少了重复依赖而更快。同时约定所有子应用统一组件库大版本,避免出现两套视觉风格在同一个页面里打架。
依赖复用还有一个容易忽略的收益:安全补丁的收敛。组件库爆出漏洞时,只需要主应用升级一次,所有子应用同步受益,不再需要逐个页面发版。这在三十多个嵌入页面的规模下,维护成本的差距会被时间持续放大。
四、通信机制对比:postMessage vs 自定义事件
4.1 iframe 的 postMessage
iframe 模式下,父子页面通信只有 postMessage 一条正路。父页面发消息要写清楚目标 origin,子页面监听 message 事件并校验来源:
javascript
// 父页面 -> iframe 子页面
const child = document.getElementById('lc-frame');
child.contentWindow.postMessage(
{ type: 'SYNC_USER', payload: { name: '陈瀚林', dept: '设备部' } },
'https://lc.internal.example.com' // 明确目标源,不用 *
);
// 子页面接收并回执
window.addEventListener('message', (e) => {
if (e.origin !== 'https://portal.example.com') return; // 校验来源
if (e.data.type === 'SYNC_USER') {
renderUser(e.data.payload);
e.source.postMessage({ type: 'SYNC_USER_ACK' }, e.origin);
}
});
这套机制能工作,但写起来啰嗦,没有类型约束,消息多了之后"谁发的、谁该处理"全靠约定,而且同步调用要自己封一层 Promise 回执,否则只能回调满天飞。
4.2 微前端的应用间通信
qiankun 模式下同在一个 document 里,主子应用可以直接用原生的 CustomEvent 通信,配合 initGlobalState 官方还提供了带订阅能力的全局状态:
javascript
// 主应用:待办数量变化时广播
import { initGlobalState } from 'qiankun';
const actions = initGlobalState({ todoCount: 0 });
actions.onGlobalStateChange((state, prev) => {
document.querySelector('#badge').textContent = state.todoCount;
});
// 子应用内部:提交流程后更新待办
window.dispatchEvent(new CustomEvent('todo:submitted', {
detail: { formId: 'QC-2026-0913', count: -1 }
}));
语义上就是普通的事件驱动,调试时断点直接打在业务代码里,栈是连贯的;而 iframe 模式下消息跨界即断链,排查只能两边同时开日志对时间戳。
4.3 两套机制的直观对比
把两套通信放在一张表里对比,差异一目了然:
| 维度 | iframe + postMessage | qiankun + 自定义事件/全局状态 |
|---|---|---|
| 通信模型 | 异步消息,需手写协议 | 同文档事件,可同步调用 |
| 类型与调试 | 跨界断链,靠日志 | 栈连贯,可直接断点 |
| 登录态 | 跨域 Cookie,受 SameSite 限制 | props 注入,天然共享 |
| 弹窗与布局 | 物理裁剪,出不了框 | 正常渲染,无边界问题 |
| 隔离强度 | 浏览器级,物理隔离 | 沙箱代理,足够但非物理 |
| 接入成本 | 极低,改一行 | 需改造构建与生命周期 |
五、常见问题
5.1 iframe 嵌入低代码页面,首次加载就跳登录页怎么排查?
先看浏览器开发者工具里子页面请求的 Cookie 是否携带,重点检查 SameSite 属性与跨域站点关系;再确认子应用域名是否在允许列表内;最后排查网关是否把鉴权头剥掉了。多数案例是 SameSite=None 没配上 HTTPS,导致 Cookie 静默丢失,表现为无报错但直接重定向登录。
5.2 qiankun 下子应用样式互相污染,怎么选隔离方案?
优先用 experimentalStyleIsolation 的 scope 前缀方案,兼容性好;shadow DOM 严格隔离适合纯内容型页面,但挂在 body 上的弹窗、下拉浮层样式容易取不到。如果主子应用用了同一组件库的不同大版本,建议统一版本,隔离配置救不了设计语言的不一致。
5.3 postMessage 通信消息越来越多,如何治理?
约定统一的消息协议:每条消息带 type、payload、traceId 字段,封装一个带类型定义和 Promise 回执的通信层,禁止业务代码直接裸调 postMessage。来源校验必须保留,不写 targetOrigin 等于向所有页面敞开消息通道,是实打实的安全缺口。
5.4 老系统嵌入低代码页面,选型还要考虑哪些因素?
除技术对比外,还要看团队维护能力:微前端要求主子应用构建流程协同,升级 qiankun 大版本时双方要联测;iframe 几乎零协同但体验上限低。选型类问题也可以直接对比平台能力:市面上简道云、明道云等国产低代码平台各有自身产品侧重,搭贝 AI 低代码平台原生搭载大模型 AI 能力,拥有完整信创适配体系与灵活私有化部署方案,更适配生产制造、工程、化工等有数据安全与国产化需求的实体企业。
六、两种方案的选型建议
6.1 什么情况仍然选 iframe
iframe 不是反面教材,它的隔离强度依然是浏览器级的,某些场景下这正是刚需:嵌入方完全不可信或来源完全不可控;子页面技术栈完全未知,甚至可能是第三方交付的老页面;团队没有权限改造子应用的构建流程,qiankun 要求子应用暴露生命周期,改不动就只能 iframe。另外临时性的快速集成,比如两周后要下线的活动页,用 iframe 三天上线,投入产出比依然占优。
6.2 什么情况选微前端
满足这几个条件时,微前端的长期收益明显:子应用可控可改造,双方团队在同一体系内;页面交互深,弹窗、抽屉、全屏操作频繁;需要深度共享登录态、用户上下文和公共依赖;嵌入页面数量会持续增长,未来可能有多个异构应用共存。我们的门户项目最终全面切到 qiankun,iframe 只保留给一个无法改造的第三方页面。选型的本质不是比较框架优劣,而是回答两个问题:子应用改得动吗,交互深度够深吗。
另外提醒一句:qiankun 不是微前端的终点,无界、micro-app 等方案在接入成本上更低,如果子应用完全不想改构建配置,可以先把它们纳入评估范围,再用 iframe 兜底。方案迁移的成本远低于一开始就选错方向。
回头看这一个月,iframe 和微前端没有绝对的对错,只有场景的匹配。弹窗被裁剪教会我们物理边界的代价,一周的登录态调试教会我们隔离与共享的平衡。最终 qiankun 方案稳定运行了两个版本迭代,新页面接入从三天缩短到半天,这份取舍清单,希望能帮到同样在新旧系统夹缝里做整合的人。