
寫在前面
“微前端”這個概念已經(jīng)在前端圈火了很長一段時間了,關(guān)于什么是微前端,微前端干了什么,其和傳統(tǒng)iframe嵌套有啥區(qū)別等等一系列疑問,社區(qū)的介紹也不少,為了保證各位同學(xué)讀到本文干貨時還保持著精力,這里對于以上概念將不做詳細贅述,當(dāng)然,為了照顧沒有了解過微前端的同學(xué),還是要做一個簡單的描述。做完概念描述后,我會用demo的方式,從基礎(chǔ)微前端到微前端的融合方案(重點!)做一個詳細講解。
概念簡答
什么是微前端
微前端是一種多個團隊通過獨立發(fā)布功能的方式來共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略。
也就是說,一個“大項目”由一個個獨立的小項目拼起來的,或者說把一個“大項目”拆成了若干的小項目,獨立開發(fā)。并且其與技術(shù)棧無關(guān),說到這里,很多人就會想到iframe,確實,通過iframe可以滿足上面我們所說的一些功能特點,但其實,我們使用的微前端是一個叫qiankun的庫,當(dāng)然了,也有人說iframe是最早的微前端實現(xiàn),那我們姑且先這么認為吧。實際上,這種說法也是有一些道理的,那么為什么我們不使用iframe呢?
這里有一篇文章,有興趣的可以看一下。大概意思就是:
- url 不同步。瀏覽器刷新 iframe url 狀態(tài)丟失、后退前進按鈕無法使用。
- UI 不同步,DOM 結(jié)構(gòu)不共享。
- 全局上下文完全隔離,內(nèi)存變量不共享。
- 慢。每次子應(yīng)用進入都是一次瀏覽器上下文重建、資源重新加載的過程。
關(guān)注過qiankun的同學(xué),應(yīng)該看得出來,上面的知識點,都搬運自官網(wǎng),如果你已經(jīng)很熟悉了,那就可以直接跳過這段了~
什么場景下適合使用微前端
關(guān)于這樣的概念和理解,我想社區(qū)中已經(jīng)數(shù)不勝數(shù)了,那么按照我的理解就是:“當(dāng)你遇到了超大型巨無霸中后臺系統(tǒng)需要優(yōu)化拆分時、當(dāng)你遇到了一個需要多個團隊維護各自模塊堆積起來的項目時”,那么請讓微前端帶你來爽一把吧。
OK~概念性的廢話就說到這,下面我將手摸手帶大家搭建一個微前端項目,并對其融合方案做研究。
搭建基礎(chǔ)微前端環(huán)境
環(huán)境準備
為簡單起見,我們直接使用umi的qiankun插件plugin-qiankun,所以,你的項目中,只需要能跑起來umi即可。
我們知道,一個微前端,是由幾個小的模塊組成的一個大的系統(tǒng),一般的,我們會把這幾個小的模塊用一個負責(zé)layout的應(yīng)用包裹,我們管這個“盒子”叫主應(yīng)用,管下面一個一個的小模塊叫子應(yīng)用。
下面,我們就使用umi作為腳手架,一個一個大分別搭建三個項目呢,其中一個為“主應(yīng)用”,另外兩個為“主應(yīng)用”
搭建項目
yarn create @umijs/umi-app
談笑間,三個項目已經(jīng)搭建完畢
其中主項目采用ant design pro模板,依賴為umi3+antd4
兩個子項目采用umi app 模板,依賴為umi3 + antd4
為了快速搭建環(huán)境,按個應(yīng)用我使用了相同的技術(shù)棧,因為umi3在集成微前端上非常方便,當(dāng)然了,大多數(shù)情況下,微前端的子應(yīng)用之間都是采用的不同的技術(shù)棧,那時候可能要大家去踩踩坑了,現(xiàn)在社區(qū)關(guān)于微前端的知識還是比較豐富的,相比解決問題比較容易,當(dāng)然了,熱心的我,也為大家提供了一些踩坑的資料
- 如果你使用的是umi2 + qiankun,那么請看這里
- 另外你可能會踩到這些坑
- 在其他技術(shù)棧中,你必須注意你的入口文件生命周期函數(shù)的導(dǎo)出和webpack出口的相關(guān)配置。
當(dāng)然了,如果你使用的是umi3,那么你可能不會踩到上面的坑,直接創(chuàng)建項目吧
我們使用umi3創(chuàng)建的項目如下:

- app-main(主應(yīng)用,啟動端口8000)
- app-one(子應(yīng)用1,啟動端口7001)
- app-two(子應(yīng)用2,啟動端口9002)
項目搭建好了,那就簡單寫點業(yè)務(wù)吧,下面是我們假設(shè)的一個需求如下,在某租房公司業(yè)務(wù)上,有一個后臺。現(xiàn)在“房源模塊”和“合同模塊”分別由兩個團隊維護,于是,我們的技術(shù)方案如下:
主應(yīng)用提供容器,兩個子應(yīng)用一個負責(zé)房源信息,一個負責(zé)合同信息。
經(jīng)過開發(fā)的不懈努力,我們的各自模塊如下。



雖然頁面略顯簡陋,但有幾個重點很突出
- 每一個項目都可以單獨啟動
- 各自有各自的啟動端口
創(chuàng)建主應(yīng)用,將子應(yīng)用裝載進去
既然主應(yīng)用是一個盒子,要裝載兩個或者多個子應(yīng)用,那么配置主應(yīng)用將比較重要,這里我們不再贅述詳細過程,大家可以參照官網(wǎng)做更詳細的了解。這里我為了布局方便,使用的是ant design pro的模板,當(dāng)然,你可以使用任何你想使用的layout,但是如果你使用的是umi,那么請安裝插件plugin-qiankun。
這里,我們也是參照參照官網(wǎng),對主應(yīng)用做了配置。
- 注冊子應(yīng)用
qiankun: {
master: {
apps: [
{
name: 'app-one', // 唯一 id
entry: '//localhost:7001',
},
{
name: 'app-two', // 唯一 id
entry: '//localhost:9001', // html entry
},
],
},
},
- 裝載子應(yīng)用
我們采用的是組件方式注冊的子組件,拿‘a(chǎn)pp-one’為例
import { MicroApp } from 'umi';
const MyPage = () => {
return (
<div>
<div>
<MicroApp
name="app-one"
autoSetLoading
className="myContainer"
wrapperClassName="myWrapper"
/>
</div>
</div>
)
}
export default MyPage
實際上,在你注冊子應(yīng)用,裝載子應(yīng)用的這個過程中,和我們react組件的路由注冊和組件創(chuàng)建非常的相似,大家可以類比著去理解。
之所以用組件式,是因為這樣,我們就可以用ant design pro的路由和菜單耦合的功能,如果你使用的其他layout做主應(yīng)用,那么你可以使用組件形式,也可以使用路由形式,這里官網(wǎng)描述的都很清楚,就不一一贅述了。我使用的是組件形式,那么我必須在路由中注冊這個頁面組件
{
path: 'app-one',
name: 'houseManage',
component: './appOne'
},
{
path: 'app-two',
name: 'hetongManage',
component: './appTwo',
exact: false, // 需要設(shè)置為fasle,這樣才能匹配到子應(yīng)用的下的子路由
},
- 子應(yīng)用配置
一個微前端子應(yīng)用,是需要做相應(yīng)的配置的,不然不會自然的集成進主應(yīng)用中。
你大概需要注意的是
- package.json中必須有一個
name的字段,來保證子應(yīng)用的唯一性 - 如果你使用的是umi3,那么你需要這樣開啟即可
qiankun: {
slave: {},
},
當(dāng)然了,在這之前,你需要安裝qiankun插件,才能在umi中配置這一項
yarn add @umijs/plugin-qiankun -D
- 如果你使用的技術(shù)棧(上文說過了,這再贅述一下),那么你還另外需要注意其入口文件的生命周期函數(shù)處理和webpack關(guān)于
output的配置,那你就必須要按照文檔的步驟來集成了。
啟動
經(jīng)過上面的一系列操作,我們的主應(yīng)用已經(jīng)可以訪問裝載的子應(yīng)用了。


小結(jié)
上面的一些列操作,都是微前端中最基礎(chǔ)的用法,如果你耐心看文檔,多踩踩坑,基本毫無壓力的就搭建出了上面的環(huán)境,當(dāng)然了,如果你已經(jīng)對搭建一個微前端環(huán)境很熟悉,你就會覺得,我上面說了一頓“廢話”,然后你就可以自動的跳過了。下面我們來探究,子應(yīng)用之間的融合。
子應(yīng)用之間的融合
通過上面的demo,我們已經(jīng)了解到了一些初步的概念,按照我們上面demo的實現(xiàn),就是說一個url(路由)對應(yīng)一個子應(yīng)用,然后這樣,我們就可以把一個大的應(yīng)用拆分成不同的小塊。
路由與應(yīng)用綁定的方式簡單直觀,是微前端中最為常見的使用場景,通常我們會用這種方式將一堆獨立域名訪問的 MPA 應(yīng)用,整合成一個一體化的 SPA 應(yīng)用。
下面我們來思考一個問題:
我們能不能在一個Url下掛載多個子應(yīng)用?
如果按照上面我們demo中的樣子,那必然是不行的,因為url的唯一性,我們只能在一個url下訪問一個唯一確定的資源,但是我們能不能解決這個問題呢?
路由模式(小插曲)
因為我們的主應(yīng)用是Spa,那么Url也就對應(yīng)了路由,下面做個小插曲,來探索一下,不同模式的路由,對于url的影響是什么,看看有沒有什么空子可以讓我們鉆一下,從而解決上面我們提出的問題。
眾所周知,Spa中的路由模式有兩種:browser、hash。其中各自的原理簡單描述如下
- 對于hash: 通過監(jiān)聽Url的
location.hash,觸發(fā)onhashchange然后拿對應(yīng)的前端資源,這里會依賴Url - 對于browser:使用HTML5的history對象中的
pushState()和replaceState()這兩個api來實現(xiàn)的跳轉(zhuǎn),加載對應(yīng)的資源。這里也會依賴Url變化。
那么,我們有沒有一種方式,可以擺脫Url的控制來加載對應(yīng)的前端資源呢?如果可以,那么我們是不是就可以在同一個Url下加載多個微前端的子應(yīng)用了呢?
實際上,路由還有另外一個不為人知的類型,那就是memory類型,這里我對memory路由類型做一個簡單的描述。
- memory類型:
不會把地址存放在URL上,而是將地址存放在本地或者數(shù)據(jù)庫中,在使用時獲取本地或者數(shù)據(jù)庫中的地址,然后匹配相對應(yīng)的資源。
看上面的描述可能有點懵逼,看一下代碼:
//例如
//獲取保存在localStorage中的地址
const path = window.localStorage.getItem('path');
//當(dāng)用戶在某個頁面就重新設(shè)置localStorage中的地址
//userHref是用戶所在的頁面
window.localStorage.setItem('path', userHref);
也就是說,我們可以把頁面存在本地或者其他地方,而脫離Url唯一性的束縛。因為,這個路由模式比較適用于“非瀏覽器”的場景,故這個路由模式也變得鮮為人知,但是他卻能很好的解決我們上面提出的問題。
當(dāng)然,也就是官網(wǎng)中說的
除了導(dǎo)航應(yīng)用之外,App1 與 App2 均依賴瀏覽器 url,為了讓 App1 嵌套 App2,兩個應(yīng)用同時存在,我們需要在運行時將 App2 的路由改為 memory 類型。
做一個子應(yīng)用嵌套的demo
umi為我們提供了MicroAppWithMemoHistory組件,我們可以直接使用,這個組件所引用的頁面或者應(yīng)用,使用的就是memory模式 ?,F(xiàn)在我們根據(jù)一個假設(shè)的需求,搭建一個嵌套的demo。
“在現(xiàn)有的項目中,我們在房源信息頁面中訪問合同列表和合同詳情”。
于是我們就可以這樣去改造我們的項目了。
在app-one“房源信息”應(yīng)用中,我們需要需要這樣修改他的配置項
qiankun: {
slave: {},
master: {
// 注冊子應(yīng)用信息
apps: [
{
name: 'app-two', // 唯一 id
entry: '//localhost:9001', // html entry
},
],
},
},
這樣,就像我們在主應(yīng)用中注冊子應(yīng)用一樣,我們在子應(yīng)用之間也是可以這樣注冊的,不同的是,我們在裝載的時候,就不能想在主應(yīng)用中裝載那樣了,這時候就是我們用MicroAppWithMemoHistory的時候。我們這樣改造了我們的"房源信息"列表
...
<>
<h1>房源信息</h1>
<Button onClick={btnClick} type="primary">查看合同列表</Button>
<Table columns={columns} dataSource={data} />
<Modal
title="合同列表"
visible={visible}
width="100%"
onOk={() => setVisible(false)}
>
// 我們可以在url出書寫子應(yīng)用中的路由,而對應(yīng)加載模塊
<MicroAppWithMemoHistory name="app-two" url="/"/>
</Modal>
</>
通過這樣的操作后,我們就可以在/app-one/這一個Url下同時加載兩個或者多個子應(yīng)用了。

當(dāng)然,我們也可以不讓其出現(xiàn)在彈窗中,設(shè)想,我們有一個中臺的分析頁面,這個頁面是由多個項目組成的,那么也就可以使用上面的場景了,我們還用我們的demo為例

可以看到,我們只需要簡單的代碼,就可以實現(xiàn)子應(yīng)用之間的嵌套了
寫在最后
關(guān)于qiankun微前端的搭建,社區(qū)的知識已經(jīng)相當(dāng)泛濫,實際上,我們面向官網(wǎng)就可以解決大部分搭建過程中遇到的坑。這篇文章的目的在于,介紹微應(yīng)用之間的融合,讓大家認識到這種開發(fā)模式。同時,知道鮮為人知的‘memory’路由模式。當(dāng)然了,微應(yīng)用之間也是可以像react組件那樣做通信的,這也是比較基礎(chǔ)的知識了,我想社區(qū)說的比我更完善吧。