vue2接入vue3,微前端初探手记(一)

1,741 阅读7分钟

我报名参加金石计划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窗口宽高,灵活定位布局。

从以上来看,已经满足我的使用场景了,我要实现的就是下面这种效果:

截屏2022-09-05 上午10.58.41.png

每次点击tab切换显示相同的iframe页面,这个页面的数据随着tab页的切换而不同,右侧的操作区域实时和页面交互。

那么就要解决以下三个问题:

  1. 初始化时,用数据渲染出frame初始表单
  2. 点击右侧操作区域,左侧iframe响应变化
  3. 操作左侧区域,页面主体可以获取到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瓶颈的方案,将在后续更新,欢迎关注~