写在前面
“微前端”这个概念已经在前端圈火了很长一段时间了,关于什么是微前端,微前端干了什么,其和传统iframe嵌套有啥区别等等一系列疑问,社区的介绍也不少,为了保证各位同学读到本文干货时还保持着精力,这里对于以上概念将不做详细赘述,当然,为了照顾没有了解过微前端的同学,还是要做一个简单的描述。做完概念描述后,我会用demo的方式,从基础微前端到微前端的融合方案(重点!)做一个详细讲解。
概念简答
什么是微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
也就是说,一个“大项目”由一个个独立的小项目拼起来的,或者说把一个“大项目”拆成了若干的小项目,独立开发。并且其与技术栈无关,说到这里,很多人就会想到iframe,确实,通过iframe可以满足上面我们所说的一些功能特点,但其实,我们使用的微前端是一个叫qiankun的库,当然了,也有人说iframe是最早的微前端实现,那我们姑且先这么认为吧。实际上,这种说法也是有一些道理的,那么为什么我们不使用iframe呢?
这里有一篇文章,有兴趣的可以看一下。大概意思就是:
- url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
- UI 不同步,DOM 结构不共享。
- 全局上下文完全隔离,内存变量不共享。
- 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
关注过qiankun的同学,应该看得出来,上面的知识点,都搬运自官网,如果你已经很熟悉了,那就可以直接跳过这段了~
什么场景下适合使用微前端
关于这样的概念和理解,我想社区中已经数不胜数了,那么按照我的理解就是:“当你遇到了超大型巨无霸中后台系统需要优化拆分时、当你遇到了一个需要多个团队维护各自模块堆积起来的项目时”,那么请让微前端带你来爽一把吧。
OK~概念性的废话就说到这,下面我将手摸手带大家搭建一个微前端项目,并对其融合方案做研究。
搭建基础微前端环境
环境准备
为简单起见,我们直接使用umi的qiankun插件plugin-qiankun,所以,你的项目中,只需要能跑起来umi即可。
我们知道,一个微前端,是由几个小的模块组成的一个大的系统,一般的,我们会把这几个小的模块用一个负责layout的应用包裹,我们管这个“盒子”叫主应用,管下面一个一个的小模块叫子应用。
下面,我们就使用umi作为脚手架,一个一个大分别搭建三个项目呢,其中一个为“主应用”,另外两个为“主应用”
搭建项目
yarn create @umijs/umi-app
谈笑间,三个项目已经搭建完毕
其中主项目采用ant design pro模板,依赖为umi3+antd4
两个子项目采用umi app 模板,依赖为umi3 + antd4
为了快速搭建环境,按个应用我使用了相同的技术栈,因为umi3在集成微前端上非常方便,当然了,大多数情况下,微前端的子应用之间都是采用的不同的技术栈,那时候可能要大家去踩踩坑了,现在社区关于微前端的知识还是比较丰富的,相比解决问题比较容易,当然了,热心的我,也为大家提供了一些踩坑的资料
当然了,如果你使用的是umi3,那么你可能不会踩到上面的坑,直接创建项目吧
我们使用umi3创建的项目如下:
- app-main(主应用,启动端口8000)
- app-one(子应用1,启动端口7001)
- app-two(子应用2,启动端口9002)
项目搭建好了,那就简单写点业务吧,下面是我们假设的一个需求如下,在某租房公司业务上,有一个后台。现在“房源模块”和“合同模块”分别由两个团队维护,于是,我们的技术方案如下:
主应用提供容器,两个子应用一个负责房源信息,一个负责合同信息。
经过开发的不懈努力,我们的各自模块如下。
虽然页面略显简陋,但有几个重点很突出
- 每一个项目都可以单独启动
- 各自有各自的启动端口
创建主应用,将子应用装载进去
既然主应用是一个盒子,要装载两个或者多个子应用,那么配置主应用将比较重要,这里我们不再赘述详细过程,大家可以参照官网做更详细的了解。这里我为了布局方便,使用的是ant design pro的模板,当然,你可以使用任何你想使用的layout,但是如果你使用的是umi,那么请安装插件plugin-qiankun。
这里,我们也是参照参照官网,对主应用做了配置。
- 注册子应用
qiankun: {
master: {
apps: [
{
name: 'app-one', // 唯一 id
entry: '//localhost:7001',
},
{
name: 'app-two', // 唯一 id
entry: '//localhost:9001', // html entry
},
],
},
},
- 装载子应用
我们采用的是组件方式注册的子组件,拿‘app-one’为例
import { MicroApp } from 'umi';
const MyPage = () => {
return (
<div>
<div>
<MicroApp
name="app-one"
autoSetLoading
className="myContainer"
wrapperClassName="myWrapper"
/>
</div>
</div>
)
}
export default MyPage
实际上,在你注册子应用,装载子应用的这个过程中,和我们react组件的路由注册和组件创建非常的相似,大家可以类比着去理解。
之所以用组件式,是因为这样,我们就可以用ant design pro的路由和菜单耦合的功能,如果你使用的其他layout做主应用,那么你可以使用组件形式,也可以使用路由形式,这里官网描述的都很清楚,就不一一赘述了。我使用的是组件形式,那么我必须在路由中注册这个页面组件
{
path: 'app-one',
name: 'houseManage',
component: './appOne'
},
{
path: 'app-two',
name: 'hetongManage',
component: './appTwo',
exact: false, // 需要设置为fasle,这样才能匹配到子应用的下的子路由
},
- 子应用配置
一个微前端子应用,是需要做相应的配置的,不然不会自然的集成进主应用中。
你大概需要注意的是
- package.json中必须有一个
name
的字段,来保证子应用的唯一性 - 如果你使用的是umi3,那么你需要这样开启即可
qiankun: {
slave: {},
},
当然了,在这之前,你需要安装qiankun插件,才能在umi中配置这一项
yarn add @umijs/plugin-qiankun -D
- 如果你使用的技术栈(上文说过了,这再赘述一下),那么你还另外需要注意其入口文件的生命周期函数处理和webpack关于
output
的配置,那你就必须要按照文档的步骤来集成了。
启动
经过上面的一系列操作,我们的主应用已经可以访问装载的子应用了。
小结
上面的一些列操作,都是微前端中最基础的用法,如果你耐心看文档,多踩踩坑,基本毫无压力的就搭建出了上面的环境,当然了,如果你已经对搭建一个微前端环境很熟悉,你就会觉得,我上面说了一顿“废话”,然后你就可以自动的跳过了。下面我们来探究,子应用之间的融合。
子应用之间的融合
通过上面的demo,我们已经了解到了一些初步的概念,按照我们上面demo的实现,就是说一个url(路由)对应一个子应用,然后这样,我们就可以把一个大的应用拆分成不同的小块。
路由与应用绑定的方式简单直观,是微前端中最为常见的使用场景,通常我们会用这种方式将一堆独立域名访问的 MPA 应用,整合成一个一体化的 SPA 应用。
下面我们来思考一个问题:
我们能不能在一个Url下挂载多个子应用?
如果按照上面我们demo中的样子,那必然是不行的,因为url的唯一性,我们只能在一个url下访问一个唯一确定的资源,但是我们能不能解决这个问题呢?
路由模式(小插曲)
因为我们的主应用是Spa,那么Url也就对应了路由,下面做个小插曲,来探索一下,不同模式的路由,对于url的影响是什么,看看有没有什么空子可以让我们钻一下,从而解决上面我们提出的问题。
众所周知,Spa中的路由模式有两种:browser
、hash
。其中各自的原理简单描述如下
- 对于hash: 通过监听Url的
location.hash
,触发onhashchange
然后拿对应的前端资源,这里会依赖Url - 对于browser:使用HTML5的history对象中的
pushState()
和replaceState()
这两个api来实现的跳转,加载对应的资源。这里也会依赖Url变化。
那么,我们有没有一种方式,可以摆脱Url的控制来加载对应的前端资源呢?如果可以,那么我们是不是就可以在同一个Url下加载多个微前端的子应用了呢?
实际上,路由还有另外一个不为人知的类型,那就是memory类型,这里我对memory路由类型做一个简单的描述。
- memory类型:
不会把地址存放在URL上,而是将地址存放在本地或者数据库中,在使用时获取本地或者数据库中的地址,然后匹配相对应的资源。
看上面的描述可能有点懵逼,看一下代码:
//例如
//获取保存在localStorage中的地址
const path = window.localStorage.getItem('path');
//当用户在某个页面就重新设置localStorage中的地址
//userHref是用户所在的页面
window.localStorage.setItem('path', userHref);
也就是说,我们可以把页面存在本地或者其他地方,而脱离Url唯一性的束缚。因为,这个路由模式比较适用于“非浏览器”的场景,故这个路由模式也变得鲜为人知,但是他却能很好的解决我们上面提出的问题。
当然,也就是官网中说的
除了导航应用之外,App1 与 App2 均依赖浏览器 url,为了让 App1 嵌套 App2,两个应用同时存在,我们需要在运行时将 App2 的路由改为 memory 类型。
做一个子应用嵌套的demo
umi为我们提供了MicroAppWithMemoHistory
组件,我们可以直接使用,这个组件所引用的页面或者应用,使用的就是memory
模式 。现在我们根据一个假设的需求,搭建一个嵌套的demo。
“在现有的项目中,我们在房源信息页面中访问合同列表和合同详情”。
于是我们就可以这样去改造我们的项目了。
在app-one“房源信息”应用中,我们需要需要这样修改他的配置项
qiankun: {
slave: {},
master: {
// 注册子应用信息
apps: [
{
name: 'app-two', // 唯一 id
entry: '//localhost:9001', // html entry
},
],
},
},
这样,就像我们在主应用中注册子应用一样,我们在子应用之间也是可以这样注册的,不同的是,我们在装载的时候,就不能想在主应用中装载那样了,这时候就是我们用MicroAppWithMemoHistory
的时候。我们这样改造了我们的"房源信息"列表
...
<>
<h1>房源信息</h1>
<Button onClick={btnClick} type="primary">查看合同列表</Button>
<Table columns={columns} dataSource={data} />
<Modal
title="合同列表"
visible={visible}
width="100%"
onOk={() => setVisible(false)}
>
// 我们可以在url出书写子应用中的路由,而对应加载模块
<MicroAppWithMemoHistory name="app-two" url="/"/>
</Modal>
</>
通过这样的操作后,我们就可以在/app-one/这一个Url下同时加载两个或者多个子应用了。
当然,我们也可以不让其出现在弹窗中,设想,我们有一个中台的分析页面,这个页面是由多个项目组成的,那么也就可以使用上面的场景了,我们还用我们的demo为例
可以看到,我们只需要简单的代码,就可以实现子应用之间的嵌套了
写在最后
关于qiankun微前端的搭建,社区的知识已经相当泛滥,实际上,我们面向官网就可以解决大部分搭建过程中遇到的坑。这篇文章的目的在于,介绍微应用之间的融合,让大家认识到这种开发模式。同时,知道鲜为人知的‘memory’路由模式。当然了,微应用之间也是可以像react组件那样做通信的,这也是比较基础的知识了,我想社区说的比我更完善吧。