基于微前端的業(yè)務(wù)邏輯拆分
一、什么是微前端?
微前端是一種多個(gè)團(tuán)隊(duì)通過獨(dú)立發(fā)布功能的方式來共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略。
微前端在2016年ThoughtWorks Technology Radar正式被提出。借鑒了微服務(wù)的架構(gòu)理念,將一個(gè)龐大的前端應(yīng)用拆分為多個(gè)獨(dú)立靈活的小型應(yīng)用,每個(gè)應(yīng)用都可以獨(dú)立開發(fā)、獨(dú)立運(yùn)行、獨(dú)立部署,再將這些小型應(yīng)用聯(lián)合為一個(gè)完整的應(yīng)用。微前端既可以將多個(gè)項(xiàng)目融合為一,又可以減少項(xiàng)目之間的耦合,提升項(xiàng)目擴(kuò)展性,相比一整塊的前端倉庫,微前端架構(gòu)下的前端倉庫傾向于更小更靈活。
架構(gòu)特點(diǎn)
- 技術(shù)棧無關(guān):主框架不限制接入應(yīng)用的技術(shù)棧,子應(yīng)用可自主選擇技術(shù)棧;
- 獨(dú)立開發(fā)/部署:各個(gè)團(tuán)隊(duì)之間倉庫獨(dú)立,單獨(dú)部署,互不依賴;
- 增量升級(jí):當(dāng)一個(gè)應(yīng)用龐大之后,技術(shù)升級(jí)或重構(gòu)相當(dāng)麻煩,而微應(yīng)用具備漸進(jìn)式升級(jí)的特性;
- 獨(dú)立運(yùn)行時(shí):微應(yīng)用之間運(yùn)行時(shí)互不依賴,有獨(dú)立的狀態(tài)管理;
- 提升效率:應(yīng)用越龐大,越難以維護(hù),協(xié)作效率越低下。微應(yīng)用可以很好拆分,提升效率。
二、目前可用的微前端方案
2.1 基于iframe完全隔離
作為前端開發(fā),我們對(duì)iframe已經(jīng)非常熟悉了,在一個(gè)應(yīng)用中可以獨(dú)立運(yùn)行另一個(gè)應(yīng)用。
優(yōu)點(diǎn):
- 非常簡單,無需任何改造;
- 完美隔離,JS、CSS都是獨(dú)立的運(yùn)行環(huán)境;
- 不限制使用,頁面上可以放多個(gè)iframe來組合業(yè)務(wù)。
缺點(diǎn):
- 無法保持路由狀態(tài),刷新后路由狀態(tài)就丟失;
- 完全的隔離導(dǎo)致與子應(yīng)用的交互變得極其困難,只能采用postMessage方式;
- iframe中的彈窗無法突破其本身;
- 整個(gè)應(yīng)用全量資源加載,加載太慢。
2.2 基于single-spa路由劫持
single-spa (opens new window)是一個(gè)目前主流的微前端采用基座技術(shù)方案,其主要實(shí)現(xiàn)思路:預(yù)先注冊(cè)子應(yīng)用,監(jiān)聽路由的變化,匹配到激活的路由則加載子應(yīng)用資源,順序調(diào)用生命周期函數(shù)(初始化,掛載,卸載)并最終渲染到容器。
優(yōu)點(diǎn):
- 有開箱即用的API;
- 技術(shù)棧無關(guān),任意技術(shù)棧均可接入;
- HTML Entry接入方式,將整個(gè)微應(yīng)用打包成一個(gè)JS文件,發(fā)布靜態(tài)資源服務(wù)器,然后在主應(yīng)用中配置該JS文件的地址告訴single-spa去這個(gè)地址加載微應(yīng)用。
缺點(diǎn):
- 對(duì)微應(yīng)用侵入性強(qiáng),會(huì)將應(yīng)用打包為一個(gè)JS文件,常用的優(yōu)化措施,如按需加載,css獨(dú)立打包等都沒有了;
- 沒有做樣式隔離,樣式容易混淆覆蓋;
- 沒有做JS隔離,JS全局對(duì)象污染;
- 無預(yù)加載機(jī)制,加載資源太慢;
- 微應(yīng)用之間沒有任何通信手段,只能用戶自己實(shí)現(xiàn)。
2.3 基于web components封裝組件
官方提出的組件化方案,它通過對(duì)組件進(jìn)行更高程度的封裝,以組件加載的方式將微應(yīng)用整合在一起實(shí)現(xiàn)應(yīng)用之間的解耦。
優(yōu)點(diǎn):
- 加載子應(yīng)用比single-spa注冊(cè)監(jiān)聽方式更加優(yōu)雅;
- 技術(shù)棧無關(guān),是瀏覽器原生組件,在任何框架中都可以使用;
- 無需與其他應(yīng)用之間產(chǎn)生任何關(guān)聯(lián);
- 應(yīng)用間采用shadow dom,隔離樣式。
缺點(diǎn):
- 瀏覽器兼容性不好。
2.4 quankun
Quankun是對(duì)single-spa做了一層封裝,主要解決了single-spa的痛點(diǎn)和不足,通過import-html-entry包解析HTML獲取資源路徑,然后對(duì)資源進(jìn)行解析,加載。
優(yōu)點(diǎn):
- 阿里團(tuán)隊(duì)開發(fā)維護(hù),文檔多,有開箱即用的API;
- JS沙箱機(jī)制,確保應(yīng)用直接變量事件不沖突;
- 資源預(yù)加載,在瀏覽器空閑時(shí)間預(yù)加載未打開的微應(yīng)用資源,加速了微應(yīng)用的打開速度;
- 可用于任意JS框架,像嵌入一個(gè)iframe,且兼容IE11。
缺點(diǎn):
- 上線部署文檔少;
- 只能解決子項(xiàng)目之間的樣式相互污染,不能解決子項(xiàng)目的樣式污染主項(xiàng)目的樣式。
2.5 EMP
主要基于Webpack5 Module Federation,是一種去中心化的微前端實(shí)現(xiàn)方案,不僅能很好的隔離應(yīng)用,還可以輕松實(shí)現(xiàn)應(yīng)用之間的資源共享和通信。
優(yōu)點(diǎn):
- 快速封裝可復(fù)用模塊,無需單獨(dú)拆包發(fā)布到NPM,可直接暴露需要共享的模塊;
- 實(shí)時(shí)動(dòng)態(tài)更新,只需要發(fā)布基站應(yīng)用,只需要訪問時(shí)刷新,即可使用最新業(yè)務(wù)模塊;
- 引入端無需手動(dòng)更新,遠(yuǎn)端模塊靈活維護(hù)和引入端可以自由組合,甚至可以運(yùn)行是引入使用遠(yuǎn)端模塊;
- 做到第三方依賴共享,使代碼盡可能地重復(fù)利用,減少加載內(nèi)容。
缺點(diǎn):
- 對(duì)webpack強(qiáng)依賴,老舊項(xiàng)目不友好;
- 沒有做CSS隔離和JS隔離,需要靠用戶自覺;
- 子應(yīng)用?;睢⒍鄳?yīng)用激活無法實(shí)現(xiàn);
- 主、子應(yīng)用的路由可能發(fā)生沖突。
三、我們要解決的問題
拆分巨型應(yīng)用,使應(yīng)用方便迭代更新,提高開發(fā)部署效率
單體應(yīng)用在一個(gè)相對(duì)長的時(shí)間跨度下,由于參與的人員、團(tuán)隊(duì)的增多、變遷,從一個(gè)普通應(yīng)用演變成一個(gè)巨石應(yīng)用(Frontend Monolith)后,隨之而來的應(yīng)用不可維護(hù)的問題。這類問題在企業(yè)級(jí) Web 應(yīng)用中尤其常見。而微前端思想可以將一個(gè)龐大的前端應(yīng)用拆分為多個(gè)獨(dú)立靈活的小應(yīng)用,每個(gè)應(yīng)用可以獨(dú)立開發(fā),獨(dú)立更新,獨(dú)立部署,減少模塊之間的耦合性,提高項(xiàng)目擴(kuò)展性,有利于各子應(yīng)用漸進(jìn)式優(yōu)化,不影響新增功能以及其他子應(yīng)用的運(yùn)行。
跨團(tuán)隊(duì)協(xié)作,實(shí)現(xiàn)需求分工,爭取資源最優(yōu)化,提高工作效率
大需求需要拆分,不同的部分需要不同的業(yè)務(wù)能力,需要涉及多個(gè)團(tuán)隊(duì)甚至多個(gè)部門協(xié)同開發(fā),所謂專業(yè)的團(tuán)隊(duì)做專業(yè)的事情,細(xì)化能力,實(shí)現(xiàn)能力效用最大化,提高團(tuán)隊(duì)整體工作效率,實(shí)現(xiàn)1+1>2的效果。
兼容歷史應(yīng)用,實(shí)現(xiàn)增量開發(fā)
舊項(xiàng)目技術(shù)棧不統(tǒng)一,微前端主框架可以不限制接入應(yīng)用的技術(shù)棧,可以隨時(shí)加載不同技術(shù)棧模塊,不需要為了兼容現(xiàn)有項(xiàng)目做大規(guī)模改造重構(gòu),提升開發(fā),測(cè)試與維護(hù)效率。
四、我們的方案
1??采用基于web component的micro-app框架為基礎(chǔ),拆分出可獨(dú)立運(yùn)行的子應(yīng)用;
2??進(jìn)一步封裝業(yè)務(wù)邏輯層,實(shí)現(xiàn)基于js沙箱與shadow dom的代碼隔離;
3??抽離子應(yīng)用的公用模塊,進(jìn)一步縮減巨石項(xiàng)目代碼規(guī)模,提高迭代維護(hù)效率。
4.1 技術(shù)特點(diǎn)
高擴(kuò)展性、獨(dú)立性、開放性:基于micro-app實(shí)現(xiàn)微前端主框架,將主應(yīng)用拆分為主平臺(tái)系統(tǒng)和各子系統(tǒng)等可以獨(dú)立交付運(yùn)行的前端子應(yīng)用,再進(jìn)一步將業(yè)務(wù)模塊抽象,公用模塊從子應(yīng)用中抽離,模塊化開發(fā)部署,提高了各個(gè)子應(yīng)用/業(yè)務(wù)模塊的可拓展性,減少耦合性,提高開發(fā)效率,降低交付成本。
統(tǒng)一功能模塊獨(dú)立性,提高安全性:用戶信息,角色權(quán)限等通用業(yè)務(wù)功可以抽象出來單獨(dú)開發(fā)部署,降低重復(fù)開發(fā),減少項(xiàng)目整理代碼量,但因?yàn)樾枰傻讲煌淖討?yīng)用,為避免和宿主頁面樣式?jīng)_突,采用基于shadow dom+JS沙箱機(jī)制,建立獨(dú)立作用域,實(shí)現(xiàn)custom element,將JS和CSS都被隔絕在dom作用域中,實(shí)現(xiàn)代碼隔離,使代碼更干凈,整潔,降低耦合性。
抽離通用功能模塊,進(jìn)一步縮減項(xiàng)目冗余:菜單,頭部,腳部,登錄,注冊(cè)等使用vite工具將其打包微ESM模塊,對(duì)于當(dāng)前單一技術(shù)棧實(shí)現(xiàn)的業(yè)務(wù)項(xiàng)目,不需要每個(gè)子應(yīng)該都重新開發(fā)公用模塊,讓代碼包總體積更小,提高開發(fā)效率,并實(shí)現(xiàn)模塊的按需異步加載,提升頁面整體加載速度。
4.2 什么是JS沙箱
JS沙箱主要是用于隔離掛載在window上的變量,保證應(yīng)用之間js環(huán)境得獨(dú)立。在子應(yīng)用運(yùn)行時(shí)訪問的window其實(shí)是一個(gè)Proxy代理對(duì)象。所有子應(yīng)用的全局變量變更都是在閉包中產(chǎn)生的,不會(huì)真正回寫到window上,這樣就能避免多實(shí)例之間的污染。
4.3 什么是shadow dom
Shadow-dom是游離在DOM樹之外的節(jié)點(diǎn)樹,他的創(chuàng)建基于自定義DOM元素(非document),并且創(chuàng)建后的shadow-dom節(jié)點(diǎn)可以從界面上直觀的看到。更重要的是,shadow-dom具有良好的密封性,可以隔離css樣式,避免css全局樣式污染。
4.4 模塊之間如何通信
該方案涉及按照業(yè)務(wù)拆分的子應(yīng)用,按照功能拆分的子模塊,他們的通信方式是不通的。
1.按照業(yè)務(wù)拆分的子應(yīng)用并不是一個(gè)模塊,而是一個(gè)可以獨(dú)立運(yùn)行的應(yīng)用,然而應(yīng)用之間的頻繁的通信會(huì)增加應(yīng)用的復(fù)雜度和耦合度,因此要盡量減少子應(yīng)用之間的通信,非必要不交互。這里我們使用micro-app官方提供的API即可實(shí)現(xiàn)。
2.按照功能模塊拆分的子模塊,既是基于web component實(shí)現(xiàn)的子模塊,之間的通信可遵循父組件加載子組件,父子組件之間的通信。