背景
本人负责的项目,是一个 toB 的大数据领域、数据中台定位的一个后台管理系统,本人负责前端侧的工程设计和开发。
为什么使用微前端?
- 历史原因
部门中的前端技术栈没有统一,有vue、React,微前端技术能使其共存。
- 从使用体验的角度
在项目的前端侧,结构方面,这个项目的特点,是很多的子应用,每个子应用有特定的功能(例如:数据管控,用于设定数据的标准、数据的表模型结构等等、系统管理,用于系统用户、角色、权限等的管理)。所有的子应用数目在 15 个以上,还保持着动态的增减,而且每个子应用的导航头部,路由菜单sider,必须样式统一,并且,出于使用体验的考虑,应用间的跳转,必须是在页面的右下侧,局部的加载,不能有整页的白屏刷新。
页面的布局如下
- 从功能的角度出发
所有的子应用都用了同一套登录系统,换言之,用户的常用业务逻辑,包括页面级、控件级的权限校验、用户信息的获取、解包都是一样的;还有一些其他的相同的业务逻辑,包括用户切换当前的【项目】,切换以后要重新局部加载;还有,对后端接口返回的 code 的回显逻辑等等。
以上的业务逻辑对应各个子应用来说都是重复的。
综上,使用微前端,能满足样式的统一,页面局部的加载跳转,还能将一些公共的业务逻辑,提取到主应用中处理,可以通过主应用将处理结果传送给子应用,减少了大量的重复的业务代码。
- 其他原因
未使用微前端以前就是一个工程对应一个子应用,基于子应用数目之多,把工程堆叠到一起显然有点笨重。并且,全部放一个工程中也会带来一些别的问题,例如因为项目太重带来的流水线 build 的耗时过久问题、多人协同开发带来的代码冲突问题等等。
首次改造
于是,我基于micro-app对现在项目进行改造。在使用微前端之初,子应用的加载是通过 ip 地址的,模式如下:
经过一段时间的使用,发现一些问题:
1 、在新的环境部署时,要重新写 ip,很繁琐且容易出错。
我经过对 docker 容器技术的学习,知道了在宿主侧、应用之间可以通过 run 的时候的服务名来互相请求访问,也就是说,当我启动前端项目a, docker run -d [子应用a的镜像id] sub-a
以后,我可以在宿主侧或者容器侧、来访问子应用a、例如curl http://sub-a
。
所以可以这样,给每个子应用一个固定的服务名,还有一个固定的访问上下文,例如:/sub-a
、/sub-b
,在主应用的 nginx 上声明这些转发规则。实现以后,就可以不用再重新部署的时候写 ip 地址了、大大提高了部署效率和准确性。
进行优化
所以我升级了项目,新的模式如下:
经过优化改造后,项目运行良好,因为不用改动子应用的 IP 地址,部署也更加轻松。
注意,nginx 规则中set
的写法是为了应对一种场景,就是当某些子应用并不需要部署,nginx 即使请求到没部署的子应用,没返回响应,nginx 也能启动而不报错。
还有,当子应用重新运行后,主应用最好也要重新运行,因为 nginx 会有 dns 缓存导致访问不到更新以后的子应用节点。
可用 demo
下面给出一个可用的模板
项目结构
- main 主应用
- sub-a 子应用 a
- sub-b 子应用 b
运行方式
先启动 docker,执行docker-compose up -d
然后访问浏览器 http://localhost
即可访问主应用
通过http://localhost/sub-a
经过主应用代理来访问子应用a
通过http://localhost/sub-b
经过主应用代理来访问子应用b
通过http://localhost:81/sub-a
直接访问子应用a
通过http://localhost:82/sub-b
直接访问子应用b
在主应用上点击对应的路由,主应用即向子应用发送对应的路由的信息,子应用收到信息后随之进行路由的跳转。
除此以外,还可以从主应用向子应用共享更多的公共业务信息。达到将公共的业务逻辑代码集中于一个地方,即主应用,并且多处复用的效果。
demo注意事项
子应用需要静态资源上下文不能为/
(例:在vue2应用中为publicPath
),以区分开子应用的静态资源与主应用的静态资源。 在上面例子中,子应用a的直接访问方式是http://localhost:81/sub-a
,因为在 子应用 a 中vue.config.js
中将 publicPath
设置为'/sub-a/'
。