什么是微前端
微前端是一種類似于微服務的架構,它將微服務的理念應用于瀏覽器端,即將 Web 應用由單一的單體應用轉變為多個小型前端應用聚合為一的應用。各個前端應用可以獨立運行、獨立開發、獨立部署。
微前端的概念由ThoughtWorks于2016年提出,此后很快被業界所接受,并在各互聯網大廠中得到推廣和應用。
微服務與微前端有很多相似之處,都是希望將某個單一的單體應用,轉化為多個可以獨立運行、獨立開發、獨立部署、獨立維護的服務或者應用的聚合,從而滿足業務快速變化及分布式多團隊并行開發的需求。微服務與微前端不僅僅是技術架構的變化,還包含了組織方式、溝通方式的變化。微服務與微前端原理和軟件工程,面向對象設計中的原理同樣相通,都是遵循單一職責(Single Responsibility)、關注分離(Separation of Concerns)、模塊化(Modularity)與分而治之(Divide & Conquer)等基本的原則。
微前端的優缺點
優點
微前端如此受到重視,是由于其特點為開發團隊帶來了大量的收益。下面就分別描述一下其優點。
可以與時俱進,不斷引入新技術/新框架
前端技術棧日新月異,推陳出新的速度絕對是冠絕群雄。如何在維護好遺留系統的前提下,不斷引入新技術和新框架,提高開發效率、質量、用戶體驗,成為每個團隊需要認真對待的問題。微前端可以很好的實現應用和服務的隔離,互相之間幾乎沒有影響,可以很好的支持團隊引入新技術和新框架。
局部/增量升級
對于許多組織來說,追求增量升級就是他們邁向微前端的第一步。對他們來說,老式的大型單體前端要么是用老舊的技術棧打造的,要么就充斥著匆忙寫成的代碼,已經到了該重寫整個前端的時候了。一次性重寫整個系統風險很大,我們更傾向一點一點換掉老的應用,同時在不受單體架構拖累的前提下為客戶不斷提供新功能。
為了做到這一點,解決方案往往就是微前端架構了。一旦某個團隊掌握了在幾乎不影響舊世界的同時為生產環境引入新功能的訣竅,其他團隊就會紛紛效仿。現有代碼仍然需要繼續維護下去,但在某些情況下還要繼續添加新功能,現在總算有了解決方案。
到最后,我們就能更隨心所欲地改動產品的各個部分,并逐漸升級我們的架構、依賴關系和用戶體驗。當主框架發生重大變化時每個微前端模塊都可以按需升級,不需要整體下線或一次性升級所有內容。如果我們想要嘗試新的技術或互動模式,也能在隔離度更好的環境下做試驗。
代碼簡潔、解耦、更易維護
微前端體系下,每個小模塊的代碼庫要比一個單體前端的代碼庫小很多。對開發者來說這些較小的代碼庫處理起來更簡單方便。而且微前端還能避免無關組件之間不必要的耦合,讓代碼更簡潔。我們可以在應用的限界上下文處劃出更明顯的界限,更好地避免無意間造成的這類耦合問題。
獨立部署
就像微服務一樣,微前端的一大優勢就是可獨立部署的能力。這種能力會縮減每次部署涉及的范圍,從而降低了風險。不管你的前端代碼是在哪里托管,怎樣托管,各個微前端都應該有自己的持續交付管道;這些管道可以將微前端構建、測試并部署到生產環境中。我們在部署各個微前端時幾乎不用考慮其他代碼庫或管道的狀態;就算舊的單體架構采用了固定、手動的按季發布周期,或者隔壁的團隊在他們的主分支里塞進了一個半成品或失敗的功能,也不影響我們的工作。如果某個微前端已準備好投入生產,那么它就能順利變為產品,且這一過程完全由開發和維護它的團隊主導
組織更具擴展能力,其團隊更加獨立自治。
解藕代碼庫、分離發布周期還能帶來一個高層次的好處,那就是大幅提升團隊的獨立性;一支獨立的團隊可以自主完成從產品構思到最終發布的完整流程,有足夠的能力獨立向客戶交付價值,從而可以更快、更高效地工作。為了實現這一目標需要圍繞垂直業務功能,而非技術功能來打造團隊。一種簡單的方法是根據最終用戶將看到的內容來劃分產品模塊,讓每個微前端都封裝應用的某個頁面,并分配給一個團隊完整負責。相比圍繞技術或“橫向”問題(如樣式、表單或驗證)打造的團隊相比,這種團隊能有更高的凝聚力。
缺點
有得必有失,在引入微前端的過程中難免會引入一些新的問題。只不過我們認為這些風險都能控制在合理水平上,微前端終究還是利大于弊的。
重復依賴
不同應用之間依賴的包存在很多重復,由于各應用獨立開發、編譯和發布,難免會存在重復依賴的情況。導致不同應用之間需要重復下載依賴,額外再增加了流量和服務端壓力。
團隊之間更加分裂
大幅提升的團隊自治水平可能會讓各個團隊的工作愈加分裂。各團隊只關注自己的業務或者平臺功能,在面向用戶的整體交付方面,會導致對用戶需求和體現不敏感,和響應不及時。
微前端應用場景
兼容遺留系統
經常會有團隊需要在兼容已有系統的前提下,使用新框架去開發新功能。遺留系統功能已經完善,并且穩定運行,團隊沒有必要,也沒有精力去將遺留系統重構一遍。此時團隊如果需要使用新框架,新技術去開發新的應用,使用微前端是很好的解決方案。
應用聚合
大型互聯網公司都會為用戶提供很多應用和服務,如何為用戶呈現具有統一用戶體驗的應用聚合成為必須解決的問題。而在大型商業公司內部,往往部署有大量的軟件服務。如何為員工提供服務聚合,提供員工工作效率,成為企業內部IT建設的重中之重。前端聚合已成為一個技術趨勢,目前比較理想的解決方案就是微前端。
團隊間共享
不用應用之間往往存在很多可以共享和功能和服務,如果在團隊之間進行高質量的共享成為提高研發效率的一條重要途徑。微前端可以采用組件或者服務的方式進行團隊間的技術共享。其低內聚高耦合的共享,使得高質量的共享成為可能。
局部/增量升級
一個大的產品由很多應用和服務組成,很多時候只需要對部分應用和服務進行升級。如果是單體應用,升級耗時長,風險高,影響可服務性。而前端可以只對需要的應用和服務進行升級,不會影響其他應用和服務。升級效率高,風險低,不影響其他應用和服務的可服務性。
微前端要克服的幾個障礙
既然微前端具有如此多的優點,您是不是已經躍躍欲試,想要馬上在團隊里引入了呢?且慢,在引入微前端的過程中會遇到一些障礙,我們先看看會遇到那些障礙吧。
資源的隔離
由于存在不同應用各自定義CSS和全局變量的情況,應用聚合時需要考慮彼此之間的影響。應用JS沙箱和CSS隔離等相關技術,使各應用之間互不影響。
應用的注冊
Html Entry 和 Config Entry,是關于如何注冊子應用信息。
對性能的影響
按需加載、公共依賴加載和預加載,是關于性能的,這些很重要,否則雖然上了微前端,但性能嚴重下降,或者由于升級引起線上故障,就得不償失了。
應用間通信
父子應用通訊,顧名思義,無需解釋。
應用嵌套/并行
子應用嵌套 和 子應用并行 是微前端的進階應用,在某些場景下會用到。
微前端實現的幾種方式
路由分發式微前端
路由分發式微前端,即通過路由將不同的業務分發到不同的、獨立前端應用上。其通常可以通過 HTTP 服務器的反向代理來實現,又或者是應用框架自帶的路由來解決。
目前,通過路由分發式的微前端架構應該是采用最多、最容易實現的 “微前端” 方案。但這種方式實際上只是多個前端應用的聚合并不是一個完整的整體。每次用戶從A應用跳轉到B應用的時候,實際上是訪問不同的html頁面。
它適用于以下場景:
- 不同技術棧之間差異比較大,難以兼容、遷移、改造
- 項目不想花費大量的時間在這個系統的改造上
- 現有的系統在未來將會被取代
- 系統功能已經很完善,基本不會有新需求
使用iFrame創建容器
使用iframe標簽嵌套將另一個HTML頁面嵌入到當前頁面中。iframe可以創建一個全新的獨立的宿主環境,這意味著我們的前端應用之間可以相互獨立運行。采用iframe有幾個重要的前提:
- 網站不需要 SEO 支持
- 擁有相應的應用管理機制。
在采用iframe的時候,我們需要做這么兩件事:
- 設計管理應用機制:在什么情況下,我們會去加載、卸載這些應用;在這個過程中,采用怎樣的動畫過渡,讓用戶看起來更加自然。
- 設計應用通訊機制:直接在每個應用中創建 postMessage 事件并監聽,并不是一個友好的事情。其本身對于應用的侵入性太強,因此通過 iframeEl.contentWindow去獲取iFrame容器內的Window對象是一個更簡化的做法。隨后,就需要定義一套通訊規范:事件名采用什么格式、什么時候開始監聽事件等等。
自制框架兼容應用
不論是基于Web Components的Angular,或者是VirtualDOM的React 等,現有的前端框架都離不開基本的HTML元素DOM。
那么,我們只需要:
- 在頁面合適的地方引入或者創建 DOM
- 用戶操作時,加載對應的應用(觸發應用的啟動),并能卸載應用。
第一個問題,創建 DOM 是一個容易解決的問題。而第二個問題,則一點兒不容易,特別是移除 DOM 和相應應用的監聽。當我們擁有一個不同的技術棧時,我們就需要有針對性設計出一套這樣的邏輯。
盡管Single-SPA已經擁有了大部分框架(如 React、Angular、Vue 等)的啟動和卸載處理,但是它仍然適合于微前端的加載和卸載。可以自己設計和實現一個微前端框架。雖然,這種方式的上手難度相對比較高,但是后期訂制及可維護性比較方便。在不考慮每次加載應用帶來的用戶體驗問題,其唯一存在的風險可能是:第三方庫不兼容。
但是,不論怎樣,與iFrame相比,其在技術上更先進。同時與iframe類似,我們仍然面對著一系列的不大不小的問題:
- 需要設計一套管理應用的機制。
- 對于流量大的toC應用來說,會在首次加載的時候,會多出大量的請求。
組合式集成:將應用微件化
組合式集成,即通過軟件工程的方式在構建前、構建時、構建后等步驟中,對應用進行一步的拆分,并重新組合。
從這種定義上來看,它可能算不上并不是一種微前端——它可以滿足了微前端的三個要素,即:獨立運行、獨立開發、獨立部署。但是,配合上前端框架的組件 Lazyload 功能——即在需要的時候,才加載對應的業務組件或應用,它看上去就是一個微前端應用。與此同時,由于所有的依賴、Pollyfill已經盡可能地在首次加載了,CSS樣式也不需要重復加載。
常見的方式有:
- 獨立構建組件和應用,生成 chunk 文件,構建后再歸類生成的 chunk 文件。(這種方式更類似于微服務,但是成本更高)
- 開發時獨立開發組件或應用,集成時合并組件和應用,最后生成單體的應用。
- 在運行時,加載應用的 Runtime,隨后加載對應的應用代碼和模板。
應用間的關系如下圖所示:
組合式集成對比
這種方式看上去相當的理想,即能滿足多個團隊并行開發,又能構建出適合的交付物。
但是,首先它有一個嚴重的限制:必須使用同一個框架。對于多數團隊來說,這并不是問題。采用微服務的團隊里,也不會因為微服務這一個前端,來使用不同的語言和技術來開發。當然了,如果要使用別的框架,也不是問題,我們只需要結合上一步中的自制框架兼容應用就可以滿足我們的需求。
其次,采用這種方式還有一個限制,那就是:規范!在采用這種方案時,我們需要:
- 統一依賴。統一這些依賴的版本,引入新的依賴時都需要一一加入。
規范應用的組件及路由。避免不同的應用之間,因為這些組件名稱發生沖突。 - 構建復雜。在有些方案里,我們需要修改構建系統,有些方案里則需要復雜的架構腳本。
- 共享通用代碼。
- 制定代碼規范。
因此,這種方式看起來更像是一個軟件工程問題。
純Web Components技術構建
Web Components是一套不同的技術,允許您創建可重用的定制元素(它們的功能封裝在您的代碼之外)并且在您的 Web 應用中使用它們。
它主要由四項技術組件:
- Custom elements,允許開發者創建自定義的元素。
- Shadow DOM,即影子 DOM,通常是將 Shadow DOM 附加到主文檔 DOM 中,并可以控制其關聯的功能。而這個 Shadow DOM 則是不能直接用其它主文檔 DOM 來控制的。
- HTML templates,即template和slot元素,用于編寫不在頁面中顯示的標記模板。
- HTML Imports,用于引入自定義組件。
每個組件由 link 標簽引入:
<link rel="import" href="components/di-li.html">
<link rel="import" href="components/d-header.html">
隨后,在各自的 HTML 文件里,創建相應的組件元素,編寫相應的組件邏輯。一個典型的Web Components應用架構如下圖所示:
Web Components 架構
可以看到這邊方式與我們上面使用iframe的方式很相似,組件擁有自己獨立的 Scripts 和 Styles,以及對應的用于單獨部署組件的域名。然而它并沒有想象中的那么美好,要直接使用純Web Components來構建前端應用的難度有:
- 重寫現有的前端應用。我們需要使用Web Components來完成整個系統的功能。
- 上下游生態系統不完善。缺乏相應的一些第三方控件支持,這也是為什么jQuery相當流行的原因。
- 系統架構復雜。當應用被拆分為一個又一個的組件時,組件間的通訊就成了一個特別大的麻煩。
Web Components中的Shadow DOM更像是新一代的前端DOM容器,遺憾的是并不是所有的瀏覽器,都可以完全支持Web Components。
結合Web Components構建
Web Components離現在的我們太遠,可是結合Web Components來構建前端應用,則更是一種面向未來演進的架構。或者說在未來的時候,我們可以開始采用這種方式來構建我們的應用。好在,已經有框架在打造這種可能性。
就當前而言,有兩種方式可以結合Web Components來構建微前端應用:
- 使用Web Components構建獨立于框架的組件,隨后在對應的框架中引入這些組件
- 在Web Components中引入現有的框架,類似于iframe的形式
前者是一種組件式的方式,或者則像是在遷移未來的 “遺留系統” 到未來的架構上。
在Web Components中集成現有框架
現有的Web框架已經有一些可以支持Web Components的形式,諸如 Angular支持的createCustomElement,就可以實現一個Web Components形式的組件:
platformBrowser()
.bootstrapModuleFactory(MyPopupModuleNgFactory)
.then(({injector}) => {
const MyPopupElement = createCustomElement(MyPopup, {injector});
customElements.define(‘my-popup’, MyPopupElement);
});
在未來,將有更多的框架可以使用類似這樣的形式,集成到Web Components應用中。
集成在現有框架中的 Web Components
另外一種方式,則是類似于Stencil的形式,將組件直接構建成Web Components形式的組件,隨后在對應的諸如,如 React 或者 Angular 中直接引用。
如下是一個在 React 中引用 Stencil 生成的Web Components的例子:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import 'test-components/testcomponents';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
在這種情況之下,我們就可以構建出獨立于框架的組件。
同樣的 Stencil 仍然也只是支持最近的一些瀏覽器,比如:Chrome、Safari、Firefox、Edge 和 IE11
復合型
復合型,就是針對實際的應用場景,選取上面的幾個類型挑幾種組合起來解決實際問題。
微前端架構選型指南
那么如何依據不同的情況來選擇合適的方案呢。
快速選型指南圖:
微前端選型指南
關鍵點的相關解釋如下:
框架限制: 在現實中多數組織需要兼容遺留系統,在技術選型上會有這樣那樣的限制。
IE問題: 不論是在幾年前,還是在今年,我們實施微前端最先考慮的就是對于IE的支持。在我遇到的項目上,基本上都需要支持IE,因此在技術選型上就受限一定的限制。而在我們那些不需要支持IE的項目上,他們就可以使用Web Components技術來構建微前端應用。
依賴獨立: 即各個微前端應用的依賴是要統一管理,還是要在各個應該中自己管理。統一管理可以解決重復加載依賴的問題,獨立管理會帶來額外的流量開銷和等待時間。
微前端方案的對比:
方式 | 開發成本 | 維護成本 | 可行性 | 同一框架要求 | 實現難度 | 潛在風險 |
---|---|---|---|---|---|---|
路由分發 | 低 | 低 | 高 | 否 | ★ | 這個方案太普通了 |
iFrame | 低 | 低 | 高 | 否 | ★ | 這個方案太普通了 |
應用微服務化 | 高 | 低 | 中 | 否 | ★★★★ | 針對每個框架做定制及 Hook |
微件化 | 高 | 中 | 低 | 是 | ★★★★★ | 針對構建系統,如 webpack 進行 hack |
微應用化 | 中 | 中 | 高 | 是 | ★★★ | 統一不同應用的構建規范 |
純 Web Components | 高 | 低 | 高 | 否 | ★★ | 新技術,瀏覽器的兼容問題 |
結合 Web Components | 高 | 低 | 高 | 否 | ★★ | 新技術,瀏覽器的兼容問題 |
同樣的,一些復雜概念的解釋如下:
應用微服務化,即每個前端應用一個獨立的服務化前端應用,并配套一套統一的應用管理和啟動機制,諸如微前端框架 Single-SPA 或者 mooa 。
微件化,即通過對構建系統的 hack,使不同的前端應用可以使用同一套依賴。它在應用微服務化的基本上,改進了重復加載依賴文件的問題。
微應用化,又可以稱之為組合式集成,即通過軟件工程的方式,在開發環境對單體應用進行拆分,在構建環境將應用組合在一起構建成一個應用。詳細的細節,可以期待后面的文章《一個單體前端應用的拆解與微服務化》
微前端方案的對比:復雜方式
之前看到一篇微服務相關的 文章,介紹了不同微服務的區別,其采用了一種比較有意思的對比方式特別詳細,這里就使用同樣的方式來展示:
架構目標 | 描述 |
---|---|
a. 獨立開發 | 獨立開發,而不受影響 |
b. 獨立部署 | 能作為一個服務來單獨部署 |
c. 支持不同框架 | 可以同時使用不同的框架,如 Angular、Vue、React |
d. 搖樹優化 | 能消除未使用的代碼 |
e. 環境隔離 | 應用間的上下文不受干擾 |
f. 多個應用同時運行 | 不同應用可以同時運行 |
g. 共用依賴 | 不同應用是否共用底層依賴庫 |
h. 依賴沖突 | 依賴的不同版本是否導致沖突 |
i. 集成編譯 | 應用最后被編譯成一個整體,而不是分開構建 |
那么,對于下表而言,表中的 a~j 分別表示上面的幾種不同的架構考慮因素。
方式 | a | b | c | d | e | f | g | h | i |
---|---|---|---|---|---|---|---|---|---|
路由分發 | O | O | O | O | O | O | |||
iFrame | O | O | O | O | O | O | |||
應用微服務化 | O | O | O | O | |||||
微件化 | O | O | - | - | O | - | |||
微應用化 | O | O | O | - | - | O | - | O | |
純Web Components | O | O | O | O | O | - | - | O | |
結合Web Components | O | O | O | O | O | O | O |
圖中的 O 表示支持,空白表示不支持,- 表示不受影響。
微前端的業務拆分
- 按照業務拆分
- 按照權限拆分
- 按照變更頻率拆分
- 按照組織結構拆分
- 跟隨后端微服務拆分
這些分類方法,直接看字面應該不會有歧義了,如何選擇,就要看實際情況了
微前端框架介紹
https://github.com/CanopyTax/single-spa
https://github.com/ionic-team/stencil
https://github.com/phodal/mooa
參考資料
書籍
《前端架構:從入門到微前端》
文章
《干貨分享:螞蟻金服前端框架和工程化實踐》
《大前端時代下的微前端架構:實現增量升級、代碼解耦、獨立部署》
《用微前端的方式搭建類單頁應用(美團微前端方案)》
《微前端的設計理念與實踐初探》
《微前端的那些事兒》
《微前端如何落地》