
本文最初發表於作者的博客網站,將作者 Ahmed Bouhlel 許可由 InfoQ 中文站翻譯分享。
現代 Web 應用正在變得越來越龐大和複雜,有時候這樣的應用會由不同的團隊來管理。應用可能會包含不同團隊開發的特性,在交付整個應用之前,我們可能希望只將某些特定的功能發布到生產環境中。如果整個應用只有一個倉庫(repo),那我們該如何管理不同的團隊和不同的發布周期呢?
這些複雜的應用大多位於客戶端,使其更加難以維護。這種單體式的臃腫應用還有一些其他的問題。在本文中,我將會討論微前端的優勢、劣勢、實現方式以及其他的內容。
微前端是一些小型的應用,大多會根據子域或功能進行劃分,它們互相協作來交付一個更大的應用。在深入介紹微前端的實現之前,我們將會闡述什麼是微前端以及為什麼要使用它。
通常,項目都有不同的規模和不同的需求。如果你的項目非常簡單,只有兩三個頁面,那麼根本沒有必要考慮微前端。你可以直接使用自己選擇的任意框架來實現,比如 Angular、React 或 Vuejs。
但是,事實並非總是如此。有時候,你的前端應用是另一個大型應用的一小部分,或者你的應用有很多的區域和特性組成,它們由不同的團隊進行開發,又或者你的應用要按特性依次發布到生產環境中。如果你正在面臨這樣的場景,那麼就需要考慮一下微前端了。我們看一下這張圖片。

如上圖所示,我們有 6 個前端應用互相協作來交付一個更大的應用。這些應用之間的通信可以藉助事件總線、window 對象或發布 / 訂閱方法來實現。每個應用都可以由不同的團隊和任意框架實現。每個應用都可以獨立地與其後端或端點進行交互。這裡有一個 bootstrap/launch 應用,它會負責加載所有其他的應用,並根據用戶的交互或路由在 DOM 中掛載或卸載它們。
這種微前端架構主要有如下的優勢:
應用會很小:顯然,當大的應用按照區域、頁面或特性進行拆分後,每個應用都會變得很小。
應用是獨立的:由於所有的應用都是單獨拆分和開發的,所以它們是相互獨立的。
應用更易於理解:因為每個應用更小,由單一團隊進行開發,所以更易於理解。
應用更易於開發和部署:由於這些應用本身都很小,都由單一的團隊進行開發,所以很易於開發和部署。我們甚至可以獨立部署它們。
應用更易於測試:我們必須為大型的應用編寫成千上萬的單元測試,並且需要一直運行。這會拖慢我們的部署過程。在實現微前端之後,每個應用都有數量更少的單元測試,並且可以獨立運行自己的單元測試。
應用的開發會更迅速:因為應用都有獨立的團隊,所以整個開發會更迅速、更容易。
CI/CD 會更簡單:每個應用都可以單獨集成和部署,這使得 CI/CD 過程會變得更加容易。當我們修復某個應用或者引入新的特性時,不用考慮整個應用的情況,因為所有的特性都是獨立的。
獨立的技術棧和版本:我們可以為每個應用選擇自己的技術棧,只不過這種情況不太多見。但是,我們可以使用相同技術棧的不同版本。例如,有些團隊可能有足夠的靈活性和時間來引入和測試同一技術棧的較新版本。
沒有共享的代碼:在大型的應用中,我們傾向於跨特性共享代碼,但是,這並不能很好地進行擴展,而且隨着應用越來越大,會引入很多缺陷和相互依賴。微前端中則沒有這樣的問題,因為我們不會共享代碼,除非它是一個啞(dumb)組件。
能夠很容易地在不影響舊有架構的情況下變更架構:有時候,我們必須要擴展舊的架構,但是可能沒有足夠的開發人員來實現或擴展架構。藉助微前端的方式,我們可以使用最新的技術棧開發新特性,並獨立進行交付。
我們看一下如何將大型應用拆分為微前端。在這方面,沒有拆分應用的具體標準,我們可以根據自己的需要以多種方式進行拆分。我們會看到各種拆分應用的方式。
這是最常見的方法,因為我們可以很容易地劃分應用的功能。例如,如果應用有三個特性,分別是 Dashboard、Profile 和 Views,我們可以將每個特性作為一個單獨的應用,並在 Launch.js 的輔助下在 DOM 中掛載和卸載它們。這個 Launch.js 可以是一個獨立的應用,也可以只是一個簡單的 JavaScript 應用。
在有些應用中,每個區域都有很多功能,例如,在 coinbase、Gmail 中。在這種情況下,我們可以將每個區域作為一個新的應用來實現。

有些應用的功能是按頁面劃分的。每個頁面都有一些獨立的功能。我們可以通過頁面來劃分應用。在下圖中,我們有四個頁面,可以分別創建四個應用。
基於域來拆分應用也是最常見的方式之一。
我們有很多實現微前端的方式,我發現最常用的是如下 6 種:
Iframes
藉助 NGINX
Web Component/Angular 元素
Angular 庫
Monorepos
定製化的編排器
微前端出現至少已經有兩年了,但它依然是一個新興領域。你可能會問有沒有相關的框架或庫幫助我們實現這種架構,從而減輕我們工作。答案是肯定的,目前已經有一些相關的庫或框架了。
single-spa
frint.js
single-spa 是一個用於前端微服務的 JavaScript 框架,可以用最流行的三個框架 / 庫來實現,即 Angular、React 和 Vue.js。它可以根據需要懶加載應用,請查閱他們的網站以了解更多信息。
frint.js 是一個模塊化的 JavaScript 框架,用於構建可擴展和反應式的應用。目前,它不支持 Angular,但支持 React。如果你要從頭開始構建一個反應式應用,而且剛剛開始的話,這個框架會特別適合你。請參閱他們的網站以了解更多信息。
有了這些基礎知識之後,我們在 single-spa 框架的協助下構建一個 Angular 項目的樣例,我希望構建一個簡單的應用以便於演示。
我們將按下圖所示,把這個應用分成多個組成部分。我們一共要實現 4 個應用,分別是 HeaderApp、DashboardApp、FooterApp 和根應用。

如下是四個應用的代碼倉庫,你可以在自己的機器上分別克隆並運行它們。
// root app runs on port 4200git clone https://github.com/ahmedbhl/micro-root.gitnpm installnpm start// micro header runs on port 4300git clone https://github.com/ahmedbhl/micro-header.gitnpm installnpm start// micro dashboard runs on port 4202git clone https://github.com/ahmedbhl/micro-dashboard.gitnpm installnpm start// micro footer runs on port 4201git clone https://github.com/ahmedbhl/micro-footer.gitnpm installnpm start然後,可以在 http://localhost:4200/ 上訪問整個應用程序
如下是根應用的 index HTML 文件。我們在第 10 行導入了這三個應用,並以適當的名稱和位置註冊了這些應用。由於我們在頁面加載時加載了所有的應用程序,所以沒有定義任何特定的上下文路徑。
<!DOCTYPE html><html> <head> <meta http-equiv="Content-Security-Policy" content="default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';"> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Your application</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="importmap-type" content="systemjs-importmap"> <script type="systemjs-importmap"> { "imports": { "footer": "http://localhost:4201/main.js", "dashboard": "http://localhost:4202/main.js", "header": "http://localhost:4300/main.js", "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.5/system/single-spa.min.js" } }</script> <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.5/system/single-spa.min.js" as="script" crossorigin="anonymous" /> <script src='https://unpkg.com/core-js-bundle@3.1.4/minified.js'></script> <script src="https://unpkg.com/zone.js"></script> <script src="https://unpkg.com/import-map-overrides@1.6.0/dist/import-map-overrides.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/system.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/amd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/named-exports.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/named-register.min.js"></script> <style></style> </head> <body> <script> System.import('single-spa').then(function (singleSpa) { singleSpa.registerApplication( 'header', function () { return System.import('header'); }, function (location) { return true; } ) singleSpa.registerApplication( 'dashboard', function () { return System.import('dashboard'); }, function (location) { // return location.pathname.startsWith('/app2'); return true; } ) singleSpa.registerApplication( 'footer', function () { return System.import('footer'); }, function (location) { // return location.pathname.startsWith('/app1'); return true; } ); singleSpa.start(); })</script> <import-map-overrides-full></import-map-overrides-full> </body></html>我們可以設置「/header」的位置路徑,這樣當瀏覽器的 URL 導航到「/header」時就會加載 header。我們來測試一下。
<script> System.import('single-spa').then(function (singleSpa) { singleSpa.registerApplication( 'header', function () { return System.import('header'); }, function (location) { return location.pathname.startsWith('/header'); // return true; })我知道微前端是一個很時尚的東西,但你不應該在每個應用中都使用它。如果你的應用程序很小,就沒有必要這樣做,不要把事情複雜化。這種方式的目的是讓我們的整個過程更加順暢,而不是增加複雜性。所以在使用該方式之前,先要進行必要的判斷。
聲明:本文為InfoQ翻譯,未經許可禁止轉載。
活動推薦
將於 11 月 21-22 日舉辦的 GMTC 全球大前端技術大會(北京站)上,來自阿里的前端技術專家光弘老師將分享《基於 LowCodeEngine 的阿里低代碼組件體系的建設和實踐》,帶你了解低代碼組件為組件研發領域帶來的變化以及機會點。此外,本次 GMTC 北京站還設置了 TypeScript、跨端技術選型、前端 DevOps 實踐、IoT 動態應用開發、大前端監控、移動端性能與效率優化等共 12 個專題,50+ 大廠技術專家現場分享,點擊底部【閱讀原文】查看更多精彩內容,感興趣的同學聯繫票務經理:+86 18514549229Webpack 創始人推出比 Webpack「快 700 倍」的 Turbopack,基於 Rust 編寫
當「增加人員」不足以解決問題,你就該考慮應用「微前端」了
WebAssembly的核心語言特性與未來發展