关于微前端-一次microApp的使用经历

13,066 阅读11分钟

背景:

业务线后台管理系统经历了数年的迭代,代码量越来越庞大
不同模块全部集成在同一个项目中,添加需求越来越难

最近,刚好又要重新开四个后台管理系统,微前端势在必行。

我认为的微前端,对于当前业务来讲的一些好处:

  • 每个子应用单独部署,可以将不同的模块分开开发,也更方便多人/部门/小组开发,互不影响,有了微前端,就可以将之前的巨石应用拆分成一个个子应用

  • 代码包方面,登录权限,包括菜单导航等等公共信息及layout样式,可以全部放在主应用中,子应用只需要关注自身的业务即可,子应用的代码更纯粹,更简单

  • 技术栈没有限制,老的项目中的技术栈是vue2+iview,里面用到的一些库都是比较老的版本,在开发新的系统时,我们当然希望能够尝试新的技术栈,比如vue3+vite,react等

选型过程

在开始之前,有简单调研过qiankun和microApp,以及看过一小部分其他实现微前端的文章,比如iframe,webpack5,web component等

也做了qiankun和microApp各自的demo感受了一下

最终选择的microApp,原因如下:

  • qiankun是以注册路由的方式,将路由与子应用关联起来,而microApp是以组件的形式

  • 新应用,我希望有一部分是通过vite来启动项目,qiankun和microApp都支持,不过qiankun需要一个社区的插件,并且不支持环境变量(此处未论证,官网有说明),而microApp虽然对vite项目有不少配置,不过支持功能较全面

在调研过程中,有一个问题对选型起到了决定性作用: 如果一个页面需要展示多个子应用要如何做,并且每个子应用都是动态填充地址的,似乎microApp以组件的形式更加的灵活。

所以最后还是选择了microApp


使用过程

这里不得不先夸赞一下microApp的文档,真的是非常全面链接在这里

每个技术栈当作主应用或者子应用的相关配置一应俱全。

在我们的业务中,技术栈主要有两部分,一是vue2+webpack,一个是vue3+vite

主应用用的也是vite + vue3

相关的配置在这里不会详细记录,毕竟按照官方文档中的配置,基本没有太多问题。

这里只简单罗列一下我们在应用中做的修改:

主应用

  • 路由采用了history模式
  • 安装microapp依赖包,通过start启动
  • 在启动中要针对vite应用做一些配置,主要是针对静态资源路径做的配置,主要是补全地址的操作,详情点击这里
  • 由于我们的子路由是动态的,所以我们通过动态路由的方式,只声明了两个路由,一个是vite,一个是vue2,因为vite子应用的沙箱功能会被屏蔽,所以默认通讯会失效,需要单独处理详情看这里
  • 在嵌入micro-app组件的时候,对于vite应用要关闭沙箱
  • 其实看下来,大多数的兼容都是为了vite子应用(不得不说,为了vite确实要付出不少)

子应用

  • 子应用全部采用hash模式
  • webpack和vite的配置可以直接复制官网文档的代码即可

开发过程

配置完成,满心欢喜地在本地启动了主应用和子应用

我估计,没启动成功的概率应该不小。

经过排查,要注意一下几个问题:

1、如果是vite应用,请注意主应用中嵌入子应用的名称,以及micro启动时候的处理的子应用名称,以及子应用中注册的mount方法,给window添加的方法名称,这些都要保持一致,才能够调用子应用的js

2、如果是webpack应用,别忘记在devServer中设置允许跨域

3、如果是vite应用,注意html中是否修改了在外层容器的id,以及在挂载的时候的id名称

4、如果以上几个问题都处理过,在开发模式下,应该就可以正常启动了

但如果是在生产或者部署之后的情况下呢?

1、首先,同样要设置跨域,但要给服务器设置允许主应用允许跨域

2、在部署之后,子应用的地址会被修改,要及时更新

业务相关

微前端启动之后,便能够开始集成业务相关的东西,这里同样给大家分享一下我在开发过程中遇到的问题

1、我们的菜单导航都是统一在主应用做的,所以第一个问题是主应用菜单点击之后,子应用不会跟着跳转路由,虽然地址变化了

解决:这里可以用官方文档提供的三种方式去解决,点这里,我们采用的是第二种,看起来第二种更加靠谱一点,也就是在主应用中点击菜单之后,要通知子应用跳转路由,通过全局通信的方式,所以在每个子应用中,都要集成一个监听一个全局通信的事件。

2、说到通信,这里也遇到了两个问题

首先第一个是在调用setGlobalData设置全局数据的时候,要将旧的全局数据加上,不然会被整个更新,也就相当于整个系统只维护一份全局通信数据

第二个问题是设置数据只支持传入对象,只支持传入对象,只支持传入对象,重要的事情说三遍,否则,容易出现监听的函数未执行的情况

3、理论上讲,当主应用开发完成之后,总不能每次开发子应用都需要开发者本地先启动一个主应用,所以应该要支持在只有子应用启动的情况下,也能看到主应用的菜单导航

解决:一开始想的,是当直接启动子应用的时候,将子应用设置为主应用,主应用设置为子应用,反过来嵌入,虽然能够实现,但总觉得代码中充斥着未知的问题

所以想了另一个办法,在主应用中开一个扩展,可以通过在url中传入参数的方式,来动态修改嵌入的子应用的地址,也就是在子应用中获取url中的参数,如果出现子应用的地址,则打开此子应用

这里要注意,vite应用不止要地址,还需要一个name,这里我们采用了动态路由的方式取获取名称

还有一点要注意,子应用必须是hash模式,否则,这样会修改主应用的路由,导致无法渲染

。。。

到此,一个基本形态的微前端就可以正常开发了,至于后续在开发过程中遇到的特殊场景,会在后续补充,感谢!


记于2022/4/24日:

需求记录

上面有提到过,开发子应用的同事其实是不需要在本地启动主应用的,最好是主应用已经部署在线上,只需要加载子应用的内容即可

问题记录

所以在单独启动子应用的时候,子应用就会被当成主应用,主应用的内容就会被当成子应用

遇到的第一个问题,也是最主要的问题,是通信问题,当应用的父子关系变化之后,通信的对象也要随之改变,这里贴一段代码

image.png

根据环境判断,来决定通信对象使用哪一个,最好是封装在一个工具库中,方便修改

这里要额外注意一下,因为vite应用取消了沙箱,所以在子应用引入主应用的地方,也需要单独注册通信对象详情查看这里

我们可以有一个约定,每个子应用都注册同一个对象,用来嵌入主应用的时候通信使用

到此,主子应用互相嵌套,通信问题便可以解决

我们可以将线上部署的主应用加载到每一个子应用中,专注子应用的业务即可!


记于2022/4/26日:

问题记录

针对上一个问题记录

当主应用路由模式为history,子应用路由模式为hash,两者主子关系反过来,路由跳转之后,会出现url错乱的问题,这个待解决,后续可尝试主子应用全部改成hash模式

但这样修改量太大,留给后面去做。

针对第一种方式,也就是在主应用中开通一个地址,获取url中的某字段,来动态修改micro-app的url地址

这里也遇到了一个小问题

是关于热更新的问题,不管是webpack还是vite都会导致热更新失效

vite失效的原因是socket获取了url的域名,但这个域名是主应用的,所以无法链接,在server中的hmr中的host,配置为子应用的ip即可

webpack热更新失效的原因是主机安全检查,只需要在devServer中修改public或者disableHostCheck设置为true,禁用此功能即可

为了方便开发,我们也可以在开发模式下,启动项目之后,将拼接好的链接打印在控制台

如果在webpack中实现此功能,可以写一个自定义plugin,获取ip,console.log即可,如下图

image.png

如果是在vite中,同样也可以写一个自定义plugin,通过configureServer钩子,保存server对象,在后续的钩子中获取到port和ip,打印即可,如下图

image.png


记于2022/4/29

我们有一个首页,有不同的模块,每个模块跳转的子应用不同

进入模块之后,会有侧边栏,侧边栏点击之后会通过全局通信的方式通知子应用跳转路由,是通过在全局通信数据中的path字段判断。

当进入A模块之后,切换了侧边栏,path字段就会保存为最后跳转的A子应用的路由

返回首页

进入B模块,path字段会导致B子应用直接跳转至A子应用的路由,页面无法渲染

解决: 每次进入首页,删除path,或者进入模块前,将path重置为对应子应用的路由


记于2022/5/1

关于主应用和子应用全都是vue-router4的情况,在子应用跳转之后,路由会错乱

在官方文档vue部分,有给出解决方案

重点:!!!在子应用卸载的同时,要将router实例和history一起卸载

详情可以点击这里


记于2022/5/16

主应用history模式,子应用使用hash模式

当使用query传参数的时候,刷新页面就会导致主应用路由解析出错

(这里主子应用都用的是vue-router4)

解决办法是在主应用后始终加一个?, 也可以始终添加一个固定的query参数

例如. www.xxx.com/main?/#/chi…


关于环境变量

因为vite子应用会需要再build的过程中转换相对地址,但我们在开发过程中,不同的环境对应的url也不同,所以需要针对环境变量去动态配置静态资源的地址

vite提供了loadEnv,用来加载我们配置的额外的环境变量

详情可以点击这里

首先,在执行yarn build的时候,配置环境变量

例如packages.json中的scripts中这样写:build:staging: 'vite build --mode staging'

那么我们就需要对应一个.env.staging的文件,其中可以设置额外的环境变量,但想要获取到必须以VITE开头

例如可以这样写:VITE_NODE_EN=staging

有多少环境配置多少种打包方式

配置好之后,我们在config中可以通过loadEnv(mode, process.cwd())的方式获取到对应的映射表

mode是什么?

看一下这段代码

export default ({ mode }) => defineConfig({

})

应该就明白了