微前端的出現的背景和意義
微前端是什麼:微前端是一種類似于微服務的架構,是一種由獨立傳遞的多個前端應用組成整體的架構風格,将前端應用分解成一些更小、更簡單的能夠獨立開發、測試、部署的應用,而在使用者看來仍然是内聚的單個産品。
微前端誕生在兩個大的背景下,在提倡擁抱變化的前端社群可以看到新的架構、技術、概念層出不窮,并且随着 Web 标準的演進,前端應用已經具備更好的性能、更快的開發效率。但随着而來的是應用的複雜程度更高、涉及的團隊規模更廣、更高的性能要求,應用複雜度已經成為阻塞業務發展的重要瓶頸。
微前端就是誕生于 Web 應用日益複雜化的場景中,因為随着網絡速度、計算機硬體水準的提升和 Web 标準的演進,過去 Web 應用使用者體驗遠不如傳統的應用軟體時代已逐漸遠去,兩者之間在使用者體驗上的差距不斷縮減,并且由于 Web
應用開發速度快、用完即走等特性,導緻的一個最終結果就是「能用 Web 技術實作的應用,最終都會通過 Web 來實作」。在近幾年湧現了一大批之前隻能在傳統 PC
軟體中才能看到的優秀産品,例如:Photoshop、Web Office、Web IDE。盡管随着 Web
标準的演進,前端工程化也在不斷演變,從子產品化到元件化在到現在的工程化,但在面對跨團隊大規模開發、跨團隊企業級應用協作,現有的分治設計模式仍然顯得有心無力。
大規模 Web 應用的困局
盡管 Web 應用的複雜度和參與人數以爆炸式的增長速度,但卻沒有一種新的架構模式來解決現有的困境,并同時兼顧 DX(developer experience)和 UX(user experience)。
以位元組跳動内「研發中台」舉例,在研發日常工作中需要使用非常多的研發系統,例如:代碼管理、代碼建構、域名管理、應用釋出、CDN 資源管理、對象存儲等。站在整個公司研發的角度考慮,最好的産品形态就是将所有的研發系統都放置同一個産品内,使用者是無法感覺他在使用不同的産品,對于使用者而言就是單個産品不存割裂感,也不需要去學習多個平台,僅僅需要學習和了解位元組跳動内的「研發中台」即可。
在位元組跳動内這一類應用随處可見,由于位元組跳動記憶體在大量業務線,每一條業務線都會誕生大量的中台系統,并且還在指數增長,以位元組跳動内電商業務舉例,對于電商營運的日常工作來說,其實與研發日常工作一樣,圍繞在:商品、商家、品牌、風控、營銷等工作上,那麼對于電商營運來說怎麼樣才最高效的電商營運系統呢,由于整個系統涉及範圍較廣,在實際的研發過程中必然會以功能或業務需求垂直的切分成更小的子系統,切分成各種小系統後盡管由于分治的設計理念提升了開發者體驗,但是一定程度上降低了使用者體驗。那能否以一種新的架構模式,既保開發者體驗,又能提升使用者體驗呢。
傳統 Web 應用的利與弊
這裡簡單分析一下傳統 Web 應用在開發大規模應用和涉及多研發團隊協作時遇到的一些困境,以上面案例中的「電商營運平台」舉例,對于電商營運而言商品、商家、品牌等都是電商營運平台能力的一部分,而不是獨立之間的孤島。若以傳統的前端研發模式進行開發,那麼此時有兩種項目設計政策:
将平台内多個系統放置同一個代碼倉庫維護 ,采用 SPA(Single-page Application) 單頁應用模式
将系統分為多個倉庫維護,在首頁聚合所有平台的入口,采用 MPA(Multi-page Application)多頁應用模式
若采用多個系統放置同一個項目内維護:
優勢:
更好的性能
具備局部更新,無縫的使用者體驗
提前預加載使用者下一頁的内容
統一的權限管控、統一的 Open API 開發能力
更好的代碼複用,基礎庫複用
統一的營運管理能力
不同系統可以很好的通信
SPA 應用特有優勢
劣勢:
代碼權限管控問題
項目建構時間長
需求釋出互相阻塞
代碼 commit 混亂、分支混亂
技術體系要求統一
無法同時灰階多條産品功能
代碼復原互相影響
錯誤監控無法細粒度拆分
采用方案一的劣勢非常明顯,在日常開發中研發:代碼建構半小時以上、釋出需求時被需求阻塞、無法局部灰階局部更新、項目遇到問題時復原影響其他業務、無法快速引進新的技術體系提高生産力,項目的疊代和維護對于研發同學而言無疑是噩夢。
盡管降低了開發體驗,如果對項目整體的代碼拆分,懶加載控制得當,其實對于使用平台的使用者而言體驗卻是提升的,這一切都歸咎于 SPA 應用帶來的優勢,SPA 應用跳轉頁面時無需重新整理整個頁面,路由變化時僅更新局部,不用讓使用者産生在 MPA
應用切換時整個頁面重新整理帶來的抖動感而降低體驗,并且由于頁面不重新整理的特性可以極大程度的複用頁面間的資源,降低切換頁面時帶來的性能損耗,使用者也不會感覺他在使用不同平台。
若采用拆分成多個倉庫維護
優勢
可以以項目次元拆分代碼,解決權限管控問題
僅建構本項目代碼,建構速度快
可以使用不同的技術體系
不存在同一個倉庫維護時的 commit 混亂和分支混亂等問題
功能灰階互不影響
劣勢
使用者在使用時體驗割裂,會在不同平台間跳轉,無法達到 SPA 應用帶來的使用者體驗
隻能以頁面次元拆分,無法拆分至區塊部分,隻能以業務為次元劃分
多系統同灰階政策困難
公共包基礎庫重複加載
不同系統間不可以直接通信
公共部分隻能每個系統獨立實作,同一運維通知困難
産品權限無法進行統一收斂
采用方案二在一定程度上提升了開發體驗,但卻降低了使用者體驗,研發在日常開發工作中需要使用大量的平台,但是卻需要跳轉到不同的平台上進行日常的研發工作,整體使用體驗較差。體驗較差的原因在于将由于通過項目次元拆分了整體「研發中台」這樣的一個産品,使各個産品之間是獨立的孤島,系統間互相跳轉都是傳統意義上的 MPA,跳轉需要重新加載整個頁面的資源,除了性能是遠不如 SPA 應用的并且應用間是沒法直接通信,這就進一步增強了使用者在使用産品時的割裂感。
背景和意義總結
通過以上兩個場景案例,其實可以發現由于 Web 應用在逐漸取代傳統的 PC 軟體時,大規模 Web 應用在面對高複雜度和涉及團隊成員廣下無法同時保證 DX 和 UX 的困境。傳統的分而治之的政策已經無法應對現代 Web
應用的複雜性,是以衍生出了微前端這樣一種新的架構模式,與後端微服務相同,它同樣是延續了分而治之的設計模式,不過卻以全新的方法來實作。
微前端解決方案
上一節總結了微前端出現的背景和意義,并且了解了兩種傳統 Web 應用的研發模式:SPA(Single-page Application)、MPA(Multi-page Application)在涉及人員廣和項目複雜度高的場景下帶來的劣勢,那麼期望能有一種新的架構能同時具備
SPA 和 MPA 兩種架構優勢,并同時提升 DX(developer experience)和 UX(user experience)呢?
那在理想的情況下,期望能達到,将一個複雜的單體應用以功能或業務需求垂直的切分成更小的子系統,并且能夠達到以下能力:
子系統間的開發、釋出從空間上完成隔離
子系統可以使用不同的技術體系
子系統間可以完成基礎庫的代碼複用
子系統間可以快速完成通信
子系統間需求疊代互不阻塞
子應用可以增量更新
子系統可以走向同一個灰階版本控制
提供集中子系統權限管控
使用者使用體驗整個系統是一個單一的産品,而不是彼此的孤島
項目的監控可以細化到到子系統
那麼基于上面理想情況,如何從零設計一套全新的架構用于解決現代 Web 應用在面對企業級系統遇到的困境呢。
微前端的整體架構
那麼如何提供一套既具備 SPA 的使用者體驗,又具備 MPA 應用帶來的靈活性,并且可以實作應用間同灰階,監控也可以細化到子系統的解決方案呢?目前在位元組跳動内應用的微前端解決方案「Garfish」就是這樣的一套方案
,該解決方案主要分為三層:部署側、架構運作時、調試工具,采用的是 SPA 的架構。
解決方案整體架構
微前端部署平台
部署平台作為微前端研發流程中重要的一環,主要提供了:微前端的服務發現、服務注冊、子應用版本控制、多個子應用間同灰階、增量更新子應用、下發子應用資訊清單,分析子應用依賴資訊提取公共基礎庫降低不同應用的依賴重複加載。
用于解決微前端中子應用的獨立部署、版本控制和子應用資訊管理,通過 Serverless 平台提供的接口或在渲染服務中下發主應用的 HTML 内容中包含子應用清單資訊,清單中包括了子應用的詳細資訊例如:應用
id、激活路徑、依賴資訊、入口資源等資訊,并通過對于子應用的公共依賴進行分析,下發子應用的公共依賴,在運作時擷取到子應用的資訊後注冊給架構,然後在主應用上控制子應用進行渲染和銷毀。
微前端運作時
Why not iframe
談到微前端繞不開的話題就是為什麼不适用 iframe 作為承載微前端子應用的容器,其實從浏覽器原生的方案來說,iframe 不從體驗角度上來看幾乎是最可靠的微前端方案了,主應用通過iframe 來加載子應用,iframe
自帶的樣式、環境隔離機制使得它具備天然的沙盒機制,但也是由于它的隔離性導緻其并不适合作為加載子應用的加載器,iframe
的特性不僅會導緻使用者體驗的下降,也會在研發在日常工作中造成較多困擾,以下總結了 iframe 作為子應用的一些劣勢:
使用iframe 會大幅增加記憶體和計算資源,因為 iframe 内所承載的頁面需要一個全新并且完整的文檔環境
iframe 與上層應用并非同一個文檔上下文導緻
主應用劫持快捷鍵操作
事件無法冒泡頂層,針對整個應用統一處理時效
事件冒泡不穿透到主文檔樹上,焦點在子應用時,事件無法傳遞上一個文檔流
跳轉路徑無法與上層文檔同步,重新整理丢失路由狀态
iframe 内元素會被限制在文檔樹中,視窗寬高限制問題
iframe 登入态無法共享,子應用需要重新登入
iframe 在禁用三方 cookie 時,iframe 平台服務不可用
iframe 應用加載失敗,内容發生錯誤主應用無法感覺
難以計算出 iframe 作為頁面一部分時的性能情況
無法預加載緩存 iframe 内容
無法共享基礎庫進一步減少包體積
事件通信繁瑣且限制多
基于 SPA 的微前端架構
盡管難以将 iframe 作為微前端應用的加載器,但是卻可以參考其設計思想,一個傳統的 iframe 加載文檔的能力可以分為四層:文檔的加載能力、HTML 的渲染、執行 JavaScript、隔離樣式和 JavaScript
運作環境。那麼微前端庫的基礎能力也可以參考其設計思想。
從設計層面采取的是基座+子應用分治的概念,部署平台負責進行服務發現和服務注冊,将注冊的應用清單資訊下發至基座,通過基座來動态控制子系統的渲染和銷毀,并提供集中式的模式來完成應用間的通信和應用的公共依賴管理,是以 Garfish 在 Runtime 層面主要提供了以下四個核心能力:
加載器(Loader)
HTML 入口類型,拆解 HTML Dom、Script、Style
JS 入口類型,提供基礎 Dom 容器
負責注冊平台側提供的應用清單
負責加載和解析子應用入口資源
預加載能力
解析子應用導出内容
沙箱隔離(Sandbox)
提供代碼執行能力,收集執行代碼時存在的副作用
提供銷毀收集副作用的能力
支援沙箱多執行個體,收集不同執行個體的副作用
路由托管(Router)
解決不同應用間的路由不同步問題
提供路由劫持能力,在主應用上管控子應用路由
提供路由驅動能力來拼裝完整的平台的能力
子應用通信(Store)鄭州無痛人流醫院http://rlmobile.zztjfk.com/
建立通信橋梁
提供共享機制
應用生命周期
整個微前端子應用的生命周期基本可以總結為:
渲染階段
若入口類型為 HTML 類型,将開始解析和拆解子應用資源
若入口類型為 JS,建立子應用的挂點 DOM
主應用通過路由驅動或手動挂載的方式觸發子應用渲染
開始加載應用的資源内容,并初始化子應用的沙箱運作時環境
判斷入口類型
将子應用存在”副作用“(對目前頁面可能産生影響的内容)交由沙箱處理
開始渲染子應用的 DOM 樹
觸發子應用的渲染 Hook
銷毀階段