我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第一篇文章,点击查看活动详情
背景
年初的时候,由于业务需要和初期设计的不合理,打算对去年做的一款基于vue2的表单设计器进行重构,说是表单设计器,又有很多业务上的复杂控件,因此开源框架都无法充分满足需求,加之vue3+vite火爆非常,故,基于vue3重新开发了一版可拖拽、可编辑、自动生成多页打印的设计器组件。
然,由于被新鲜技术迷了双眼,没有考虑主程序是个vue2项目,也没充分调研vue2是否可以直接使用vue3项目,在时间紧迫,加之,产品项目侧私自承诺了客户上线时间,没有充分的时间去做方案选型。于是采用了最简单直接的方案——iframe。
这篇文章不是对表单设计器思路的叙述,打算在进行一次重构之后再做分享记录。这里想记录一下,使用iframe接入以及后续带来了各种问题,以及改造踩坑的全过程。
万能的iframe
其实无论是iframe,还是其他的微前端框架,希望达到的效果都是不同项目之间的快速接入,满足不同模块之前的独立开发部署,实现js,css的充分隔离。因此,在项目进度及其紧张的时候,优先考虑的技术选型就是iframe。
sandbox
sandbox
特性(attribute)允许在<iframe>
中禁止某些特定行为,以防止其执行不被信任的代码。它通过将 iframe 视为非同源的,或者应用其他限制来实现 iframe 的“沙盒化”。对于
<iframe sandbox src="...">
,有一个应用于其上的默认的限制集。但是,我们可以通过提供一个以空格分隔的限制列表作为特性的值,来放宽这些限制,该列表中的各项为不应该应用于这个 iframe 的限制,例如:<iframe sandbox="allow-forms allow-popups">
。换句话说,一个空的
"sandbox"
特性会施加最严格的限制,但是我们用一个以空格分隔的列表,列出要移除的限制。
优点
- iframe提供了一个完整的sandbox环境,他的所有行为都是独立的,也就可以原封不动的把嵌入的网页展示出来。
- 由于内容和行为的独立,如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
- 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。例如,常见的pc网站,通过路由来实现对不同网站的跳转和嵌套。
- 可以快速接入第三方弹窗和广告之类的,不影响主业务线。
- 可以预设iframe窗口宽高,灵活定位布局。
从以上来看,已经满足我的使用场景了,我要实现的就是下面这种效果:
每次点击tab切换显示相同的iframe页面,这个页面的数据随着tab页的切换而不同,右侧的操作区域实时和页面交互。
那么就要解决以下三个问题:
- 初始化时,用数据渲染出frame初始表单
- 点击右侧操作区域,左侧iframe响应变化
- 操作左侧区域,页面主体可以获取到iframe内部的数据
交互
主应用注册监听
registerEvents() {
window.addEventListener("message", (e) => {
try {
const { type, data, pageId } = e.data;
const arg = { data, originEvent: e };
if (type === '111') {
// do something
}
} catch (err) {
console.error("主应用接收到消息失败", {info: { version, name }},err);
}
});
}
- type: 对应的事件名
- data:子应用向主应用传输的数据
- pageId: 页面隔离标识
这个应用接入的坑在于,主应用只有一个,但是多个tab页签都有自己的事件和数据流,因此避免事件互串,加了个pageId作为前置判断,对于每个子应用只内嵌一次且事件不会存在冲突的场景,是不需要加这个参数的。
子应用发送事件
window.parent.postMessage(
{
type: 'aaa',
data: JSON.stringify(pageData),
pageId: id
},
'*'
);
子应用,只需要在特定的实际,上报给主应用它需要的数据,并携带上当前的pageId,这个pageId来源于当前tab页的唯一标识,并通过url拼接参数的方式注入到页面,这样可以能做到有效的事件隔离。
子页面的事件监听
window.addEventListener('message', async (e) => {
switch (e.data.type) {
// do something...
}
}
这里就比较简单了,无非就是监听message事件,对对应想要关注的事件进行处理。
基于以上几个简单有效的api,项目成功在deadline之前上线。
万恶的iframe
上线归上线,问题总是要面对的,iframe这种粗暴简单的接入方式,必然会带来很多隐患和问题。
1.慢
客户叫的最欢的一个问题,慢!
每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程,也就是相当于一个网站的初次打开,必然会面对一个问题,首屏渲染的问题。
客户对于一个系统完全重0到1的加载具有一定的包容度。但是对于网站加载后的内嵌的页面,从一个客户的角度来看,需要再来一次0到1的渲染过程几乎是不能容忍的,更何况我每次增加tab都需要经历一次这样的过程。
解决方案
- 对于子应用做一些最常见的优化:如静态资源压缩,异步加载,分包,gz压缩。
- 在tab页面切换的时候通过v-show做页面缓存。
然而,这样的优化也只是治标不治本,页面的渲染和加载过程是无论如何的无法避免的。
2.焦点问题
iframe内部相当于一个独立的页面,当在主页面做一些如拖拽或者主动聚焦功能时,子页面区域就是一个很大的隐患,一旦误操作,焦点便是不可控的。
同样的,在iframe内部做一些表单焦点处理时,一旦对外部页面做了操做,内部的焦点同样会失控。
解决方案
主应用某些特定操作可以对子应用增加遮罩层,以解决拖动失焦的问题。
然而,也无法彻底解决焦点失控的问题。
3.SEO
代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。
这个问题对于我们这种toB的内网项目确实可以忽略不计,但也是既存的问题。
4.兼容性
很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
同样的,目前项目的使用场景都是PC端,可忽略此问题。
5.内存堆积问题
项目需要频繁的创建销毁iframe,必然的会保留一些残留,这对于我们的项目几乎是毁灭性的问题。
因为我们的客户是医院的医生,且是基于一个自研的工作站使用,这样会导致医生长时间的打开工作站进行工作,一整天不会重启或重新打开页面,这对于工作站来说,内存的不断堆积基本上是毁灭性的,会导致患者特别多的时候,到了傍晚,内存打满,工作站卡死。
解决方案
用 iframe.document.write('')
; 将内容清空。
但是这样处理之后任然会有500-1000K左右的内存残留,这就是 ie6 的 iframe bug!!!
动态创建的iframe总会耗费掉一些内存。
iframe总结
iframe的接入最大的有点是,快速,且方便迭代,真实的隔离,提供一个完美的沙箱环境。
但是带来的问题也不可忽视的,从目前客户反馈的加载慢和焦点混乱的问题也是使用iframe目前无法克服的技术难题。
因此,在成功上线,满足客户基本使用的前提下,保留目前的方案的基础上,对其他的微前端方案进行调研。
广受吹捧的qiankun或其他
目前正在调研其他更优秀的,可以解决iframe瓶颈的方案,将在后续更新,欢迎关注~