字節(jié)跳動(dòng)是如何落地微前端的
?? 加個(gè)關(guān)注,后續(xù)上新不錯(cuò)過~
本文內(nèi)提及的 Garfish 微前端解決方案已開源:https://github.com/modern-js-dev/garfish(目前的 Garfish 作為字節(jié)跳動(dòng)各部門應(yīng)用最廣泛的微前端解決方案已經(jīng)服務(wù)超過 100+ 前端團(tuán)隊(duì),400+ 項(xiàng)目),另外字節(jié)跳動(dòng)的現(xiàn)代 Web 工程體系即將開源(Modern.js),深度集成 Garfish 提供了對微前端的原生支持,提供更開箱即用的能力,敬請期待!
微前端的出現(xiàn)的背景和意義
微前端是什么:微前端是一種類似于微服務(wù)的架構(gòu),是一種由獨(dú)立交付的多個(gè)前端應(yīng)用組成整體的架構(gòu)風(fēng)格,將前端應(yīng)用分解成一些更小、更簡單的能夠獨(dú)立開發(fā)、測試、部署的應(yīng)用,而在用戶看來仍然是內(nèi)聚的單個(gè)產(chǎn)品。
微前端誕生在兩個(gè)大的背景下,在提倡擁抱變化的前端社區(qū)可以看到新的框架、技術(shù)、概念層出不窮,并且隨著 Web 標(biāo)準(zhǔn)的演進(jìn),前端應(yīng)用已經(jīng)具備更好的性能、更快的開發(fā)效率。但隨著而來的是應(yīng)用的復(fù)雜程度更高、涉及的團(tuán)隊(duì)規(guī)模更廣、更高的性能要求,應(yīng)用復(fù)雜度已經(jīng)成為阻塞業(yè)務(wù)發(fā)展的重要瓶頸。
微前端就是誕生于 Web 應(yīng)用日益復(fù)雜化的場景中,因?yàn)殡S著網(wǎng)絡(luò)速度、計(jì)算機(jī)硬件水平的提升和 Web 標(biāo)準(zhǔn)的演進(jìn),過去 Web 應(yīng)用用戶體驗(yàn)遠(yuǎn)不如傳統(tǒng)的應(yīng)用軟件時(shí)代已逐漸遠(yuǎn)去,兩者之間在用戶體驗(yàn)上的差距不斷縮減,并且由于 Web 應(yīng)用開發(fā)速度快、用完即走等特性,導(dǎo)致的一個(gè)最終結(jié)果就是「能用 Web 技術(shù)實(shí)現(xiàn)的應(yīng)用,最終都會(huì)通過 Web 來實(shí)現(xiàn)」。在近幾年涌現(xiàn)了一大批之前只能在傳統(tǒng) PC 軟件中才能看到的優(yōu)秀產(chǎn)品,例如:Photoshop、Web Office、Web IDE。盡管隨著 Web 標(biāo)準(zhǔn)的演進(jìn),前端工程化也在不斷演變,從模塊化到組件化在到現(xiàn)在的工程化,但在面對跨團(tuán)隊(duì)大規(guī)模開發(fā)、跨團(tuán)隊(duì)企業(yè)級應(yīng)用協(xié)作,現(xiàn)有的分治設(shè)計(jì)模式仍然顯得有心無力。

大規(guī)模 Web 應(yīng)用的困局
盡管 Web 應(yīng)用的復(fù)雜度和參與人數(shù)以爆炸式的增長速度,但卻沒有一種新的架構(gòu)模式來解決現(xiàn)有的困境,并同時(shí)兼顧 DX(developer experience)和 UX(user experience)。
以字節(jié)跳動(dòng)內(nèi)「研發(fā)中臺(tái)」舉例,在研發(fā)日常工作中需要使用非常多的研發(fā)系統(tǒng),例如:代碼管理、代碼構(gòu)建、域名管理、應(yīng)用發(fā)布、CDN 資源管理、對象存儲(chǔ)等。站在整個(gè)公司研發(fā)的角度考慮,最好的產(chǎn)品形態(tài)就是將所有的研發(fā)系統(tǒng)都放置同一個(gè)產(chǎn)品內(nèi),用戶是無法感知他在使用不同的產(chǎn)品,對于用戶而言就是單個(gè)產(chǎn)品不存割裂感,也不需要去學(xué)習(xí)多個(gè)平臺(tái),僅僅需要學(xué)習(xí)和了解字節(jié)跳動(dòng)內(nèi)的「研發(fā)中臺(tái)」即可。
在字節(jié)跳動(dòng)內(nèi)這一類應(yīng)用隨處可見,由于字節(jié)跳動(dòng)內(nèi)存在大量業(yè)務(wù)線,每一條業(yè)務(wù)線都會(huì)誕生大量的中臺(tái)系統(tǒng),并且還在指數(shù)增長,以字節(jié)跳動(dòng)內(nèi)電商業(yè)務(wù)舉例,對于電商運(yùn)營的日常工作來說,其實(shí)與研發(fā)日常工作一樣,圍繞在:商品、商家、品牌、風(fēng)控、營銷等工作上,那么對于電商運(yùn)營來說怎么樣才最高效的電商運(yùn)營系統(tǒng)呢,由于整個(gè)系統(tǒng)涉及范圍較廣,在實(shí)際的研發(fā)過程中必然會(huì)以功能或業(yè)務(wù)需求垂直的切分成更小的子系統(tǒng),切分成各種小系統(tǒng)后盡管由于分治的設(shè)計(jì)理念提升了開發(fā)者體驗(yàn),但是一定程度上降低了用戶體驗(yàn)。那能否以一種新的架構(gòu)模式,既保開發(fā)者體驗(yàn),又能提升用戶體驗(yàn)?zāi)亍?/p>
傳統(tǒng) Web 應(yīng)用的利與弊
這里簡單分析一下傳統(tǒng) Web 應(yīng)用在開發(fā)大規(guī)模應(yīng)用和涉及多研發(fā)團(tuán)隊(duì)協(xié)作時(shí)遇到的一些困境,以上面案例中的「電商運(yùn)營平臺(tái)」舉例,對于電商運(yùn)營而言商品、商家、品牌等都是電商運(yùn)營平臺(tái)能力的一部分,而不是獨(dú)立之間的孤島。若以傳統(tǒng)的前端研發(fā)模式進(jìn)行開發(fā),那么此時(shí)有兩種項(xiàng)目設(shè)計(jì)策略:
將平臺(tái)內(nèi)多個(gè)系統(tǒng)放置同一個(gè)代碼倉庫維護(hù) ,采用 SPA(Single-page Application) 單頁應(yīng)用模式 將系統(tǒng)分為多個(gè)倉庫維護(hù),在首頁聚合所有平臺(tái)的入口,采用 MPA(Multi-page Application)多頁應(yīng)用模式
若采用多個(gè)系統(tǒng)放置同一個(gè)項(xiàng)目內(nèi)維護(hù):
優(yōu)勢: 更好的性能 具備局部更新,無縫的用戶體驗(yàn) 提前預(yù)加載用戶下一頁的內(nèi)容 統(tǒng)一的權(quán)限管控、統(tǒng)一的 Open API 開發(fā)能力 更好的代碼復(fù)用,基礎(chǔ)庫復(fù)用 統(tǒng)一的運(yùn)營管理能力 不同系統(tǒng)可以很好的通信 SPA 應(yīng)用特有優(yōu)勢: 劣勢: 代碼權(quán)限管控問題 項(xiàng)目構(gòu)建時(shí)間長 需求發(fā)布相互阻塞 代碼 commit 混亂、分支混亂 技術(shù)體系要求統(tǒng)一 無法同時(shí)灰度多條產(chǎn)品功能 代碼回滾相互影響 錯(cuò)誤監(jiān)控?zé)o法細(xì)粒度拆分
采用方案一的劣勢非常明顯,在日常開發(fā)中研發(fā):代碼構(gòu)建半小時(shí)以上、發(fā)布需求時(shí)被需求阻塞、無法局部灰度局部升級、項(xiàng)目遇到問題時(shí)回滾影響其他業(yè)務(wù)、無法快速引進(jìn)新的技術(shù)體系提高生產(chǎn)力,項(xiàng)目的迭代和維護(hù)對于研發(fā)同學(xué)而言無疑是噩夢。
盡管降低了開發(fā)體驗(yàn),如果對項(xiàng)目整體的代碼拆分,懶加載控制得當(dāng),其實(shí)對于使用平臺(tái)的用戶而言體驗(yàn)卻是提升的,這一切都?xì)w因于 SPA 應(yīng)用帶來的優(yōu)勢,SPA 應(yīng)用跳轉(zhuǎn)頁面時(shí)無需刷新整個(gè)頁面,路由變化時(shí)僅更新局部,不用讓用戶產(chǎn)生在 MPA 應(yīng)用切換時(shí)整個(gè)頁面刷新帶來的抖動(dòng)感而降低體驗(yàn),并且由于頁面不刷新的特性可以極大程度的復(fù)用頁面間的資源,降低切換頁面時(shí)帶來的性能損耗,用戶也不會(huì)感知他在使用不同平臺(tái)。
若采用拆分成多個(gè)倉庫維護(hù)
優(yōu)勢 可以以項(xiàng)目維度拆分代碼,解決權(quán)限管控問題 僅構(gòu)建本項(xiàng)目代碼,構(gòu)建速度快 可以使用不同的技術(shù)體系 不存在同一個(gè)倉庫維護(hù)時(shí)的 commit 混亂和分支混亂等問題 功能灰度互不影響 劣勢 用戶在使用時(shí)體驗(yàn)割裂,會(huì)在不同平臺(tái)間跳轉(zhuǎn),無法達(dá)到 SPA 應(yīng)用帶來的用戶體驗(yàn) 只能以頁面維度拆分,無法拆分至區(qū)塊部分,只能以業(yè)務(wù)為維度劃分 多系統(tǒng)同灰度策略困難 公共包基礎(chǔ)庫重復(fù)加載 不同系統(tǒng)間不可以直接通信 公共部分只能每個(gè)系統(tǒng)獨(dú)立實(shí)現(xiàn),同一運(yùn)維通知困難 產(chǎn)品權(quán)限無法進(jìn)行統(tǒng)一收斂
采用方案二在一定程度上提升了開發(fā)體驗(yàn),但卻降低了用戶體驗(yàn),研發(fā)在日常開發(fā)工作中需要使用大量的平臺(tái),但是卻需要跳轉(zhuǎn)到不同的平臺(tái)上進(jìn)行日常的研發(fā)工作,整體使用體驗(yàn)較差。體驗(yàn)較差的原因在于將由于通過項(xiàng)目維度拆分了整體「研發(fā)中臺(tái)」這樣的一個(gè)產(chǎn)品,使各個(gè)產(chǎn)品之間是獨(dú)立的孤島,系統(tǒng)間相互跳轉(zhuǎn)都是傳統(tǒng)意義上的 MPA,跳轉(zhuǎn)需要重新加載整個(gè)頁面的資源,除了性能是遠(yuǎn)不如 SPA 應(yīng)用的并且應(yīng)用間是沒法直接通信,這就進(jìn)一步增強(qiáng)了用戶在使用產(chǎn)品時(shí)的割裂感。
背景和意義總結(jié)
通過以上兩個(gè)場景案例,其實(shí)可以發(fā)現(xiàn)由于 Web 應(yīng)用在逐步取代傳統(tǒng)的 PC 軟件時(shí),大規(guī)模 Web 應(yīng)用在面對高復(fù)雜度和涉及團(tuán)隊(duì)成員廣下無法同時(shí)保證 DX 和 UX 的困境。傳統(tǒng)的分而治之的策略已經(jīng)無法應(yīng)對現(xiàn)代 Web 應(yīng)用的復(fù)雜性,因此衍生出了微前端這樣一種新的架構(gòu)模式,與后端微服務(wù)相同,它同樣是延續(xù)了分而治之的設(shè)計(jì)模式,不過卻以全新的方法來實(shí)現(xiàn)。
微前端解決方案
上一節(jié)總結(jié)了微前端出現(xiàn)的背景和意義,并且了解了兩種傳統(tǒng) Web 應(yīng)用的研發(fā)模式:SPA(Single-page Application)、MPA(Multi-page Application)在涉及人員廣和項(xiàng)目復(fù)雜度高的場景下帶來的劣勢,那么期望能有一種新的架構(gòu)能同時(shí)具備 SPA 和 MPA 兩種架構(gòu)優(yōu)勢,并同時(shí)提升 ?DX(developer experience)和 UX(user experience)呢?
那在理想的情況下,期望能達(dá)到,將一個(gè)復(fù)雜的單體應(yīng)用以功能或業(yè)務(wù)需求垂直的切分成更小的子系統(tǒng),并且能夠達(dá)到以下能力:
子系統(tǒng)間的開發(fā)、發(fā)布從空間上完成隔離 子系統(tǒng)可以使用不同的技術(shù)體系 子系統(tǒng)間可以完成基礎(chǔ)庫的代碼復(fù)用 子系統(tǒng)間可以快速完成通信 子系統(tǒng)間需求迭代互不阻塞 子應(yīng)用可以增量升級 子系統(tǒng)可以走向同一個(gè)灰度版本控制 提供集中子系統(tǒng)權(quán)限管控 用戶使用體驗(yàn)整個(gè)系統(tǒng)是一個(gè)單一的產(chǎn)品,而不是彼此的孤島 項(xiàng)目的監(jiān)控可以細(xì)化到到子系統(tǒng)
那么基于上面理想情況,如何從零設(shè)計(jì)一套全新的架構(gòu)用于解決現(xiàn)代 Web 應(yīng)用在面對企業(yè)級系統(tǒng)遇到的困境呢。
微前端的整體架構(gòu)
那么如何提供一套既具備 SPA 的用戶體驗(yàn),又具備 MPA 應(yīng)用帶來的靈活性,并且可以實(shí)現(xiàn)應(yīng)用間同灰度,監(jiān)控也可以細(xì)化到子系統(tǒng)的解決方案呢?目前在字節(jié)跳動(dòng)內(nèi)應(yīng)用的微前端解決方案「Garfish」就是這樣的一套方案 ,該解決方案主要分為三層:部署側(cè)、框架運(yùn)行時(shí)、調(diào)試工具,采用的是 SPA 的架構(gòu)。
解決方案整體架構(gòu)


微前端部署平臺(tái)
部署平臺(tái)作為微前端研發(fā)流程中重要的一環(huán),主要提供了:微前端的服務(wù)發(fā)現(xiàn)、服務(wù)注冊、子應(yīng)用版本控制、多個(gè)子應(yīng)用間同灰度、增量升級子應(yīng)用、下發(fā)子應(yīng)用信息列表,分析子應(yīng)用依賴信息提取公共基礎(chǔ)庫降低不同應(yīng)用的依賴重復(fù)加載。
用于解決微前端中子應(yīng)用的獨(dú)立部署、版本控制和子應(yīng)用信息管理,通過 Serverless 平臺(tái)提供的接口或在渲染服務(wù)中下發(fā)主應(yīng)用的 HTML 內(nèi)容中包含子應(yīng)用列表信息,列表中包括了子應(yīng)用的詳細(xì)信息例如:應(yīng)用 id、激活路徑、依賴信息、入口資源等信息,并通過對于子應(yīng)用的公共依賴進(jìn)行分析,下發(fā)子應(yīng)用的公共依賴,在運(yùn)行時(shí)獲取到子應(yīng)用的信息后注冊給框架,然后在主應(yīng)用上控制子應(yīng)用進(jìn)行渲染和銷毀。

微前端運(yùn)行時(shí)
Why not iframe
談到微前端繞不開的話題就是為什么不適用 iframe 作為承載微前端子應(yīng)用的容器,其實(shí)從瀏覽器原生的方案來說,iframe 不從體驗(yàn)角度上來看幾乎是最可靠的微前端方案了,主應(yīng)用通過iframe 來加載子應(yīng)用,iframe 自帶的樣式、環(huán)境隔離機(jī)制使得它具備天然的沙盒機(jī)制,但也是由于它的隔離性導(dǎo)致其并不適合作為加載子應(yīng)用的加載器,iframe 的特性不僅會(huì)導(dǎo)致用戶體驗(yàn)的下降,也會(huì)在研發(fā)在日常工作中造成較多困擾,以下總結(jié)了 iframe 作為子應(yīng)用的一些劣勢:
使用iframe 會(huì)大幅增加內(nèi)存和計(jì)算資源,因?yàn)?iframe 內(nèi)所承載的頁面需要一個(gè)全新并且完整的文檔環(huán)境 iframe 與上層應(yīng)用并非同一個(gè)文檔上下文導(dǎo)致 主應(yīng)用劫持快捷鍵操作 事件無法冒泡頂層,針對整個(gè)應(yīng)用統(tǒng)一處理時(shí)效 事件冒泡不穿透到主文檔樹上,焦點(diǎn)在子應(yīng)用時(shí),事件無法傳遞上一個(gè)文檔流 跳轉(zhuǎn)路徑無法與上層文檔同步,刷新丟失路由狀態(tài) iframe 內(nèi)元素會(huì)被限制在文檔樹中,視窗寬高限制問題 iframe 登錄態(tài)無法共享,子應(yīng)用需要重新登錄 iframe 在禁用三方 cookie 時(shí),iframe 平臺(tái)服務(wù)不可用 iframe 應(yīng)用加載失敗,內(nèi)容發(fā)生錯(cuò)誤主應(yīng)用無法感知 難以計(jì)算出 iframe 作為頁面一部分時(shí)的性能情況 無法預(yù)加載緩存 iframe 內(nèi)容 無法共享基礎(chǔ)庫進(jìn)一步減少包體積 事件通信繁瑣且限制多
基于 SPA 的微前端架構(gòu)
盡管難以將 iframe 作為微前端應(yīng)用的加載器,但是卻可以參考其設(shè)計(jì)思想,一個(gè)傳統(tǒng)的 iframe 加載文檔的能力可以分為四層:文檔的加載能力、HTML 的渲染、執(zhí)行 JavaScript、隔離樣式和 JavaScript 運(yùn)行環(huán)境。那么微前端庫的基礎(chǔ)能力也可以參考其設(shè)計(jì)思想。
從設(shè)計(jì)層面采取的是基座+子應(yīng)用分治的概念,部署平臺(tái)負(fù)責(zé)進(jìn)行服務(wù)發(fā)現(xiàn)和服務(wù)注冊,將注冊的應(yīng)用列表信息下發(fā)至基座,通過基座來動(dòng)態(tài)控制子系統(tǒng)的渲染和銷毀,并提供集中式的模式來完成應(yīng)用間的通信和應(yīng)用的公共依賴管理,因此 Garfish 在 Runtime 層面主要提供了以下四個(gè)核心能力:
加載器(Loader) HTML 入口類型,拆解 HTML Dom、Script、Style JS 入口類型,提供基礎(chǔ) Dom 容器 負(fù)責(zé)注冊平臺(tái)側(cè)提供的應(yīng)用列表 負(fù)責(zé)加載和解析子應(yīng)用入口資源 預(yù)加載能力 解析子應(yīng)用導(dǎo)出內(nèi)容 沙箱隔離(Sandbox) 提供代碼執(zhí)行能力,收集執(zhí)行代碼時(shí)存在的副作用 提供銷毀收集副作用的能力 支持沙箱多實(shí)例,收集不同實(shí)例的副作用 路由托管(Router) 解決不同應(yīng)用間的路由不同步問題 提供路由劫持能力,在主應(yīng)用上管控子應(yīng)用路由 提供路由驅(qū)動(dòng)能力來拼裝完整的平臺(tái)的能力 子應(yīng)用通信(Store) 建立通信橋梁 提供共享機(jī)制

應(yīng)用生命周期
整個(gè)微前端子應(yīng)用的生命周期基本可以總結(jié)為:
渲染階段 若入口類型為 HTML 類型,將開始解析和拆解子應(yīng)用資源 若入口類型為 JS,創(chuàng)建子應(yīng)用的掛點(diǎn) DOM 主應(yīng)用通過路由驅(qū)動(dòng)或手動(dòng)掛載的方式觸發(fā)子應(yīng)用渲染 開始加載應(yīng)用的資源內(nèi)容,并初始化子應(yīng)用的沙箱運(yùn)行時(shí)環(huán)境 判斷入口類型 將子應(yīng)用存在”副作用“(對當(dāng)前頁面可能產(chǎn)生影響的內(nèi)容)交由沙箱處理 開始渲染子應(yīng)用的 DOM 樹 觸發(fā)子應(yīng)用的渲染 Hook 銷毀階段 若路由變化離開子應(yīng)用的激活范圍或主動(dòng)觸發(fā)銷毀函數(shù),觸發(fā)應(yīng)用的銷毀 清除應(yīng)用在渲染時(shí)和運(yùn)行時(shí)產(chǎn)生的副作用 移除子應(yīng)用的 DOM 元素

加載器的設(shè)計(jì)
加載器的整體設(shè)計(jì)理念其實(shí)與 React-loadable 非常類似,具備以下能力:
異步加載組件資源 可以預(yù)加載資源 可以緩存組件資源 緩存組件實(shí)例
與組件不同的是微前端作為一種能夠?qū)误w應(yīng)用拆解成多個(gè)子應(yīng)用的架構(gòu)模式,不同于組件,這些被拆分出去的子應(yīng)用最好的研發(fā)模式是在開發(fā)、測試、部署都與宿主環(huán)境分離,子應(yīng)用本身應(yīng)具備自治能力,那么此時(shí)就與 iframe 提供的能力非常類似,iframe 通過加載 HTML 文檔的形式加載整個(gè)子應(yīng)用的資源,那么子應(yīng)用本身就可作為一個(gè)獨(dú)立站點(diǎn),天然具備獨(dú)立開發(fā)、測試的能力。因此 Garfish 的加載器提供了兩種應(yīng)用入口類型:HTML 類型和 JS 入口類型,但需要注意的是 Garfish 并非像 iframe 一樣將其分為了另一個(gè)文檔流,而是將其與主應(yīng)用作為同一個(gè)文檔流處理,用以規(guī)避其不再同一個(gè)文檔流帶來的體驗(yàn)感割裂問題。
由于 HTML 入口類型天然具備獨(dú)立、開發(fā)、測試的特性,在微前端整體架構(gòu)設(shè)計(jì)中,對于跨團(tuán)隊(duì)協(xié)作而言,最好的研發(fā)模式是能降低其溝通成本,而降低溝通成本的最好方式是不溝通,所以一般項(xiàng)目類型都盡可能的推薦用戶使用 HTML 的入口類型。
那么針對 HTML 入口類型的加載器需要做一些什么呢,下面是一張瀏覽器的渲染過程圖:

針對瀏覽器的渲染過程也可將其分為:HTML 文本下載、 HTML 拆解為語法樹、拆解語法樹中具備”副作用的內(nèi)容“(對當(dāng)前頁面可能產(chǎn)生影響的內(nèi)容)如 Script、Style、Link 并交由沙箱處理進(jìn)行后渲染,與一般的子應(yīng)用不同的是需要子應(yīng)用提供 provider,provider 中包含了子應(yīng)用渲染和銷毀的生命周期,這兩個(gè) Hook 可以應(yīng)用緩存模式中進(jìn)一步增強(qiáng)應(yīng)用的渲染速度和性能。

沙箱的設(shè)計(jì)
為什么需要沙箱
其實(shí)在過去的 Web 應(yīng)用中是很少提及到沙箱這一概念的,因?yàn)榻M件的開發(fā)一般都會(huì)由研發(fā)通過研發(fā)規(guī)范來盡可能的去避免組件對當(dāng)前應(yīng)用環(huán)境造成副作用,諸如:組件渲染后添加了定時(shí)器、全局變量、滾動(dòng)事件、全局樣式并且在組件銷毀后會(huì)及時(shí)的清除子應(yīng)用對當(dāng)前環(huán)境產(chǎn)生的副作用。
與組件完全不同的是微前端是由多個(gè)獨(dú)立運(yùn)行的應(yīng)用組成的架構(gòu)風(fēng)格,這些系統(tǒng)可能分別來自不同的技術(shù)體系。項(xiàng)目的開發(fā)、測試從空間和時(shí)間上都是分離的,由于沒有 iframe 一樣原生能力的隔離很難應(yīng)用間不發(fā)生沖突,這些沖突可能會(huì)導(dǎo)致應(yīng)用發(fā)生異常、報(bào)錯(cuò)、甚至不可用等狀態(tài)。
以 Webpack4 JsonpFunction 為例
在 Webpack5 中提供了一個(gè)重要的功能就是 Module Federation,隨著 Webpack 5 推出 Module Federation ,與 Webpack 4 發(fā)生變化的一個(gè)重要配置就是 JsonpFunction 屬性變?yōu)榱?chunkLoadingGlobal,并且由原來的默認(rèn)值 webpackJsonp 變成了默認(rèn)使用 output.library 名稱或者上下文中的 package.json 的 包名稱(package name)作為唯一值(webpack.js.org/issues/3940)。
為什么會(huì)發(fā)生這個(gè)轉(zhuǎn)變呢,如果了解過 Webpack 構(gòu)建產(chǎn)物的一定會(huì)知道 Webpack 通過全局變量存儲(chǔ)了分 chunk 后的產(chǎn)物,用于解決分包 chunk 的加載問題。由于 Webpack 5 引入 Module Federation 頁面中可能會(huì)同時(shí)存在兩個(gè)以上的 Webpack 構(gòu)建產(chǎn)物,如果還是通過是通過同一個(gè)變量存儲(chǔ)要加載的 chunk ,必然會(huì)造成產(chǎn)物之間的互相影響。
通過 Webpack 4 到 Webpack 5 支持 Module Federation 之后可以發(fā)現(xiàn),在一個(gè)基礎(chǔ)庫尚未考慮默認(rèn)兼容多實(shí)例的場景下,貿(mào)然將其作為多實(shí)例使用很可能會(huì)造成應(yīng)用無法按照預(yù)期運(yùn)行,更為嚴(yán)重的是你以為其正常運(yùn)行了其實(shí)應(yīng)用已經(jīng)發(fā)生了嚴(yán)重的內(nèi)存泄漏或不可預(yù)知的情況,倘若將 Webpack 構(gòu)建產(chǎn)物的應(yīng)用多次動(dòng)態(tài)的在頁面中運(yùn)行,將會(huì)發(fā)現(xiàn)已經(jīng)造成嚴(yán)重的內(nèi)存泄漏,因?yàn)?Webpack 會(huì)增量的向全局存儲(chǔ) chunk 的變量上掛載模塊以及依賴信息,簡單來說就是每次執(zhí)行 Webpack 構(gòu)建的子應(yīng)用代碼都會(huì)向 webpackJsonp 數(shù)組 push 大量的數(shù)據(jù),最終造成內(nèi)存泄漏,直至頁面崩潰。
沙箱的核心能力
為了保證應(yīng)用能夠穩(wěn)定的運(yùn)行且互不影響,需要提供安全的運(yùn)行環(huán)境,能夠有效地隔離、收集、清除應(yīng)用在運(yùn)行期間所產(chǎn)生的副作用,那應(yīng)用運(yùn)行期間主要會(huì)產(chǎn)生哪些副作用呢,可以將其分為以下幾類:全局變量、全局事件、定時(shí)器、網(wǎng)絡(luò)請求、localStorage、Style 樣式、DOM 元素。
在 Garfish Runtime 中的沙箱主要能力也是圍繞在這一塊的能力建設(shè)上,針對子應(yīng)用可能產(chǎn)生的副作用類型主要分為兩類,一類是:靜態(tài)副作用、另一類則是:動(dòng)態(tài)副作用。這里靜態(tài)副作用和動(dòng)態(tài)副作用分別指的是什么呢,靜態(tài)副作用指的是 HTML 中靜態(tài)標(biāo)簽內(nèi)容例如:Script 標(biāo)簽、Style 標(biāo)簽、Link 標(biāo)簽,這些內(nèi)容屬于在 HTML 文檔流中就包含的,另外一部分副作用屬于動(dòng)態(tài)副作用,動(dòng)態(tài)副作用指的是由 JavaScript 動(dòng)態(tài)創(chuàng)建出來的,例如 JavaScript 可以動(dòng)態(tài)創(chuàng)建 Style、動(dòng)態(tài)創(chuàng)建 Script、動(dòng)態(tài)創(chuàng)建 Link、動(dòng)態(tài)執(zhí)行代碼、動(dòng)態(tài)添加 DOM 元素、添加全局變量、添加定時(shí)器、網(wǎng)絡(luò)請求、localStorage 等對當(dāng)前頁面產(chǎn)生副作用的內(nèi)容。
針對子應(yīng)用的靜態(tài)副作用的收集比較簡單,Loader 核心模塊上已經(jīng)提供了子應(yīng)用入口資源類型的分析和拆解,可以從子應(yīng)用 DOM 樹中輕松拆解獲取副作用內(nèi)容,那么對于靜態(tài)副作用已經(jīng)可以完成有效的收集、清除,但是尚未具備隔離的能力。動(dòng)態(tài)創(chuàng)建的副作用都是通過 JavaScript 來動(dòng)態(tài)創(chuàng)建的,需要收集到 JavaScript 運(yùn)行時(shí)產(chǎn)生的副作用,并提供副作用的隔離和銷毀能力。
沙箱設(shè)計(jì)的兩種思路
在 Garfish 微前端中,如何有效收集、隔離、清除應(yīng)用的副作用是保障應(yīng)用能夠平穩(wěn)運(yùn)行的核心能力之一。沙箱的主要能力也在于能夠捕獲動(dòng)態(tài)創(chuàng)建的副作用,對應(yīng)用的副作用進(jìn)行隔離和清除。
那么如何能夠有效的捕獲到動(dòng)態(tài)創(chuàng)建的副作用、收集、并隔離呢?目前 Garfish 提供了兩種設(shè)計(jì)思路,一種是快照模式,另外一種是 VM 模式。
快照沙箱
顧名思義,在應(yīng)用運(yùn)行前通過快照的模式來保存當(dāng)前執(zhí)行環(huán)境,在應(yīng)用銷毀后恢復(fù)回應(yīng)用之前的執(zhí)行環(huán)境,用于實(shí)現(xiàn)應(yīng)用間副作用的隔離和清除。類似于 “SL 大法”,通過 save 存儲(chǔ)環(huán)境,通過 load 加載環(huán)境的模式。
代碼實(shí)現(xiàn)思路

核心設(shè)計(jì)思想簡述:
針對每一種副作用提供一個(gè) Patch 類,這個(gè)類需要提供 save 和 load 兩個(gè)方法 Save 對應(yīng)著該副作用的環(huán)境快照存儲(chǔ),Load 對應(yīng)著銷毀該副作用的銷毀恢復(fù)環(huán)境 并且針對每一種 Patch 還可以存儲(chǔ)其在運(yùn)行期間發(fā)生的變化,在優(yōu)化場景時(shí)并不用所有代碼,僅恢復(fù)執(zhí)行環(huán)境即可
VM 沙箱
通過快照沙箱的最簡化的核心實(shí)現(xiàn)后可以發(fā)現(xiàn),它的設(shè)計(jì)理念依賴于整個(gè)代碼的執(zhí)行屬于線性的過程,即:存儲(chǔ)執(zhí)行環(huán)境=>執(zhí)行具備副作用的代碼=>恢復(fù)執(zhí)行環(huán)境,但在實(shí)際的場景中對于應(yīng)用的劃分并以頁面為維度劃分,同一個(gè)頁面可能存在多個(gè)應(yīng)用,所以它的執(zhí)行順序并非線性,可能同時(shí)存在多個(gè)快照沙箱的實(shí)例環(huán)境,也就是快照沙箱多實(shí)例,以下面代碼舉例:

通過上面的代碼可以發(fā)現(xiàn),在同時(shí)運(yùn)行多個(gè)快照沙箱實(shí)例時(shí),在代碼執(zhí)行順序非線性的場景下,并不能有效的收集和處理應(yīng)用的副作用,也基于此快照沙箱無法使用在非線性呢多實(shí)例的場景中,因此也進(jìn)一步推出了 VM(virtual machine) 沙箱。
維基百科關(guān)于 VM ?的解釋:在計(jì)算機(jī)科學(xué)中的體系結(jié)構(gòu)里,是指一種特殊的軟件,可以在計(jì)算機(jī)平臺(tái)和終端用戶之間創(chuàng)建一種環(huán)境,而終端用戶則是基于虛擬機(jī)這個(gè)軟件所創(chuàng)建的環(huán)境來操作其它軟件。虛擬機(jī)(VM)是計(jì)算機(jī)系統(tǒng)的仿真器,通過軟件模擬具有完整硬件系統(tǒng)功能的、運(yùn)行在一個(gè)完全隔離環(huán)境中的完整計(jì)算機(jī)系統(tǒng),能提供物理計(jì)算機(jī)的功能。
在 Node 中也提供了 VM 模塊,不過不過不同于傳統(tǒng)的 VM,它并不具備虛擬機(jī)那么強(qiáng)的隔離性,并沒有從模擬完整的硬件系統(tǒng),僅僅將指定代碼放置了特定的上下文中編譯并執(zhí)行代碼,所以它無法用于不可信來源的代碼。
參考 Node 中 VM 模塊的設(shè)計(jì),以及 JavaScript 詞法作用域 的特性,可以設(shè)計(jì)出 VM 沙箱,不過與傳統(tǒng)的 VM 差異也同樣存在,它并能執(zhí)行不可信的代碼,因?yàn)樗母綦x能力僅限于將其運(yùn)行在一個(gè)指定的上下文環(huán)境中。
從而得出以下設(shè)計(jì)


隔離環(huán)境需要哪些上下文
針對副作用的類型:全局變量、全局事件、定時(shí)器、網(wǎng)絡(luò)請求、localStorage、Style 樣式、DOM 元素,分別提供了全新的執(zhí)行上下文:
Window 用于隔離全局環(huán)境 document 收集 DOM 副作用 收集 Style 副作用,進(jìn)行處理 收集 Script,繼續(xù)放置沙箱處理 用于捕獲動(dòng)態(tài)創(chuàng)建的 DOM 節(jié)點(diǎn)、Style、Script timeout、interval 處理定時(shí)器 localstorage 隔離 localStorage listener 收集全局事件
新的執(zhí)行上下文哪里來
新的執(zhí)行上下文有兩個(gè)來源,
來源于當(dāng)前環(huán)境 來源于 iframe 的執(zhí)行環(huán)境
由于 iframe 創(chuàng)建后需要需要較多的內(nèi)存資源和計(jì)算資源,而微前端中的 VM 沙箱并不需要一個(gè)完全的執(zhí)行上下文,所以可以基于當(dāng)前環(huán)境。

快照沙箱和 VM 沙箱能力對比

路由系統(tǒng)的設(shè)計(jì)
在于現(xiàn)代 MVC 的設(shè)計(jì)思想,前端框架的設(shè)計(jì)思想也一直在發(fā)生變更,現(xiàn)代 Web 前端框架提供的最經(jīng)典的能力莫過于將 MVC 中的 Constroller 變?yōu)榱?Router,目前幾乎主流的前端框架都支持路由驅(qū)動(dòng)視圖,僅提供一個(gè) Router Map 路由表,無需關(guān)注控制任何路由狀態(tài)即可完成跳轉(zhuǎn)后的路由更新。
通過微前端出現(xiàn)的背景和意義,可以了解到微前端主要是用于解決:應(yīng)用增量升級、多技術(shù)體系并存、構(gòu)建大規(guī)模企業(yè)級 Web 應(yīng)用而誕生的。那么在基于 SPA 的微前端架構(gòu)中也可以了解到,目前微前端主要是采用應(yīng)用分而治之 + 動(dòng)態(tài)加載 + SPA 應(yīng)用的模式來解決大規(guī)模應(yīng)用帶來的一系列問題。在以組件為顆粒度的 SPA 應(yīng)用中組件內(nèi)部是不需要關(guān)心路由的,但是在微前端中主要通過應(yīng)用維度來拆分,那么拆分的應(yīng)用也可能是一個(gè)獨(dú)立的 SPA 應(yīng)用,那么此時(shí)主應(yīng)用與子應(yīng)用的關(guān)系如何編排呢?
微前端應(yīng)用中理想的路由調(diào)度
假設(shè)存在一個(gè) Garfish 站點(diǎn),這個(gè)站點(diǎn)它是由主應(yīng)用+三個(gè)子應(yīng)用構(gòu)成,主應(yīng)用的 basename 為 /demo,并存在三個(gè) Tab 分別指向跳轉(zhuǎn)至不同的應(yīng)用,理想的路由效果:
在點(diǎn)擊 vue-app Tab,跳轉(zhuǎn)至 /demo/vue-app路由后,分別激活vue-app下,為 Vue 類型的 A 應(yīng)用和 B 應(yīng)用,并激活 A 應(yīng)用和 B 應(yīng)用中的 Home 組件點(diǎn)擊 React-app Tab 進(jìn)入到 /demo/react-app路由后,分別激活react-app下,為 React 類型的 C 應(yīng)用,并激活 C 應(yīng)用的 Home 組件在激活 C 應(yīng)用的基礎(chǔ)上,點(diǎn)擊 Detail 按鈕,跳轉(zhuǎn)至 /demo/react-app/detail,并激活 C 應(yīng)用的 detail 組件。點(diǎn)擊瀏覽器返回按鈕展示,跳轉(zhuǎn) /demo/react-app/detail,并激活 C 應(yīng)用的 Home 組件,至此完成瀏覽器的基本路由跳轉(zhuǎn)能力。

不考慮任何路由處理的場景
假設(shè)存在一個(gè) Garfish 站點(diǎn),這個(gè)站點(diǎn)它是由主應(yīng)用+一個(gè)子應(yīng)用構(gòu)成。由于 Garfish 采用的是 ?SPA 架構(gòu),子應(yīng)用與主應(yīng)用所處于同一個(gè)執(zhí)行上下文,子應(yīng)用的路由原樣反應(yīng)在主應(yīng)用上。

那么此時(shí)分別跳轉(zhuǎn)到:/home、/detail路由會(huì)發(fā)現(xiàn)哪些問題呢?
假定跳轉(zhuǎn)的方法可以同時(shí)觸發(fā)主子應(yīng)用路由更新,主應(yīng)用路由和子應(yīng)用路由會(huì)同時(shí)發(fā)生搶占情況,后渲染的組件會(huì)覆蓋先渲染的路由組件 在觸發(fā)路由跳轉(zhuǎn)方后,只有主應(yīng)用視圖觸發(fā)刷新、只有子應(yīng)用視圖刷新、或都不刷新 「視圖的路由狀態(tài)維護(hù)在框架內(nèi)部」,通過原生跳轉(zhuǎn)無法觸發(fā)視圖更新
此時(shí)當(dāng)分別跳轉(zhuǎn)到:/home、/detail、/test 路由時(shí)分別觸發(fā)對應(yīng)的組件視圖,但是倘若子應(yīng)用路由中也存在 /detail視圖呢,由于應(yīng)用的開發(fā)采用分治的模式,應(yīng)用的開發(fā)從空間和時(shí)間上都是分離的,無法保證應(yīng)用間的路由不發(fā)生路由搶占的情況。
「通過 history 路由跳轉(zhuǎn)無法保證應(yīng)用能夠觸發(fā)視圖更新」,在通過 history api 進(jìn)行路由跳轉(zhuǎn)時(shí),是無法觸發(fā)應(yīng)用視圖更新,假設(shè)存在一個(gè) React 應(yīng)用 A,存在一個(gè)組件視圖 Test,分別通過 React 提供的路由方法跳轉(zhuǎn)和原生的路由跳轉(zhuǎn)進(jìn)行觀察:

Hash 和 History 路由模式
目前主流的 SPA 前端應(yīng)用基本上都支持兩種路由模式,一種是:hash 模式、另一種則是 History 路由模式,兩者的優(yōu)劣和使用并不在本文的討論范圍之內(nèi),這里僅做在微前端這種分離式開發(fā)模式下的介紹,在微前端這種分離式 SPA 應(yīng)用開發(fā)的模式下該選擇哪種路由模式,以及多 SPA 應(yīng)用下他們的路由應(yīng)該如何編排:
假設(shè)站點(diǎn)地址為:http://garfish.bytedance.net
正常路由情況
主應(yīng)用 history 模式、子應(yīng)用 history 模式
主應(yīng)用(
basename: /example):主應(yīng)用所有路由基于:
http://garfish.bytedance.net/example例例如跳轉(zhuǎn)到:/appA,
http://garfish.bytedance.net/example/appA/子子應(yīng)用(
basename: /example/appA):子應(yīng)用所有路由基于:
http://garfish.bytedance.net/example/appA跳轉(zhuǎn)到子應(yīng)用的 /detail 頁,
http://garfish.bytedance.net/example/appA/detail特點(diǎn):
當(dāng)主子應(yīng)用分別為 history 模式時(shí),子應(yīng)用的路由基于主應(yīng)用基礎(chǔ)路由并帶上自己的業(yè)務(wù)路由
路由同步到主應(yīng)用路由上,通過 子應(yīng)用 scope 命名空間隔離(子應(yīng)用 A,提供 appA 的 scope)主應(yīng)用和其他應(yīng)用的路由沖突,并將子應(yīng)用
路徑符合用戶和開發(fā)者認(rèn)知和理解
支持嵌套層級使用,并繼續(xù)通過 scope 的命名空間保證路由可讀
主應(yīng)用 history 模式、子應(yīng)用 hash 模式
主應(yīng)用(
basename: /example):主應(yīng)用所有路由基于:
http://garfish.bytedance.net/example例如跳轉(zhuǎn)到:/appA,
http://garfish.bytedance.net/example/appA/子應(yīng)用(
basename: /example/appA):子應(yīng)用所有路由基于:
http://garfish.bytedance.net/example/appA從主應(yīng)用:
http://garfish.bytedance.net/example/appA,跳轉(zhuǎn)到子應(yīng)用的?/detail 頁,http://garfish.bytedance.net/example/appA#/detail特特點(diǎn):
在一定程度上具備主子應(yīng)用都為 history 模式的優(yōu)勢,不支持嵌套層級使用
目前多數(shù)框架都不支持以 hash 值作為 basename
可讀性尚可
異常路由情況
主應(yīng)用 hash 模式、子應(yīng)用 history 模式
主應(yīng)用(
basename: /example):主應(yīng)用所有路由基于:
http://garfish.bytedance.net/example例如跳轉(zhuǎn)到:/detail,
http://garfish.bytedance.net/example#/appA子應(yīng)用(
basename: /example#/appA):子應(yīng)用所有路由基于:
http://garfish.bytedance.net/example#/appA跳轉(zhuǎn)到子應(yīng)用的 /detail 頁,
http://garfish.bytedance.net/example/detail#/appA特點(diǎn):
「路由混亂」,不符合用戶和開發(fā)者直覺
目前多數(shù)框架都不支持以 hash 值作為 basename
主應(yīng)用 hash 模式、子應(yīng)用 hash 模式
主應(yīng)用(
basename: /example):主應(yīng)用所有路由基于:
http://garfish.bytedance.net/example例如跳轉(zhuǎn)到:/detail,
http://garfish.bytedance.net/example#/appA子應(yīng)用(
basename: /example#/appA):子應(yīng)用所有路由基于:
http://garfish.bytedance.net/example#/appA跳轉(zhuǎn)到子應(yīng)用的 /detail 頁,
http://garfish.bytedance.net/example#/detail特點(diǎn):
「路由混亂」,不符合用戶和開發(fā)者直覺
目前多數(shù)框架都不支持以 hash 值作為 basename
可能與主應(yīng)用或其他子應(yīng)用發(fā)生路由沖突
Garfish ?Router 如何處理路由
通過上面理想的路由模式案例發(fā)現(xiàn),微前端應(yīng)用拆分成子應(yīng)用后,子應(yīng)用路由應(yīng)具備自治能力,可以充分的利用應(yīng)用解耦后的開發(fā)優(yōu)勢,但與之對應(yīng)的是應(yīng)用間的路由可能會(huì)發(fā)生沖突、兩種路由模式下可能產(chǎn)生用戶難以理解的路由狀態(tài)、無法激活不同前端框架的下帶來的視圖無法更新等問題。
目前 Garfish 主要提供了以下四條策略
提供 Router Map,減少典型中臺(tái)應(yīng)用下的開發(fā)者理解成本 為不同子應(yīng)用提供不同的 basename 用于隔離應(yīng)用間的路由搶占問題 路由發(fā)生變化時(shí)能準(zhǔn)確激活并觸發(fā)應(yīng)用視圖更新
Router Map 降低開發(fā)者理解成本
在典型的中臺(tái)應(yīng)用中,通常可以將應(yīng)用的結(jié)構(gòu)分為兩塊,一塊是菜單另一塊則是內(nèi)容區(qū)域,依托于現(xiàn)代前端 Web 應(yīng)用的設(shè)計(jì)理念的啟發(fā),通過提供路由表來自動(dòng)化完成子應(yīng)用的調(diào)度,將公共部分作為拆離后的子應(yīng)用渲染區(qū)域。


自動(dòng)計(jì)算出子應(yīng)用所需的 basename
當(dāng)應(yīng)用處于激活狀態(tài)時(shí),根據(jù)應(yīng)用的激活條件自動(dòng)計(jì)算出應(yīng)用所需的基礎(chǔ)路徑,并在渲染時(shí)告訴框架,以便于應(yīng)用間路由不發(fā)生沖突。

如何有效的觸發(fā)不同應(yīng)用間的視圖更新
目前主流框架實(shí)現(xiàn)路由的方式并不是監(jiān)聽路由變化觸發(fā)組件更新,讓開發(fā)者通過框架包裝后的 API 進(jìn)行跳轉(zhuǎn),并內(nèi)部維護(hù)路由狀態(tài),在使用框架提供 API 方法發(fā)生路由更新時(shí),內(nèi)部狀態(tài)發(fā)生變更觸發(fā)組件更新。
由于框架的路由狀態(tài)分別維護(hù)在各自的內(nèi)部,那么如何保證在路由發(fā)生變化時(shí)能及時(shí)有效的觸發(fā)應(yīng)用的視圖更新呢,答案是可以的,目前主要有兩種實(shí)現(xiàn)策略:
收集框架監(jiān)聽的 popstate 事件 主動(dòng)觸發(fā) popstate 事件
因?yàn)槟壳爸С?SPA 應(yīng)用的前端框架都會(huì)監(jiān)聽瀏覽器后退事件,在瀏覽器后退時(shí)根據(jù)路由狀態(tài)觸發(fā)應(yīng)用視圖的更新,那么其實(shí)也可以利用這種能力主動(dòng)觸發(fā)應(yīng)用視圖的更新,可以通過收集框架的監(jiān)聽事件,也可以觸發(fā) popstate 來響應(yīng)應(yīng)用的 popstate 事件
基于「現(xiàn)代 Web 框架」的微前端最佳實(shí)踐
微前端作為一種全新的 Web 應(yīng)用類型,不同于以往傳統(tǒng)的 Web 應(yīng)用開發(fā),微前端需要采用主子應(yīng)用分治的開發(fā)模式后帶來了一系列新的挑戰(zhàn),這些挑戰(zhàn)包括但不限于:主子應(yīng)用開發(fā)調(diào)試、普通 Web 應(yīng)用如何快速變?yōu)槲⑶岸藨?yīng)用、如何支持微前端應(yīng)用 SSR、主子應(yīng)用數(shù)據(jù)通信觸發(fā)視圖更新。Modern.js 作為 Garfish 上層的現(xiàn)代 Web 框架,能夠很好的解決這些問題,并提供開箱即用的開發(fā)體驗(yàn)。
微前端應(yīng)用的調(diào)試開發(fā)
由于微前端應(yīng)用采用分治的開發(fā)策略,應(yīng)用間的維護(hù)和開發(fā)可能在時(shí)間和空間上都是分離的,那么在開發(fā)環(huán)境時(shí)啟動(dòng)整個(gè)微前端項(xiàng)目的所有主子應(yīng)用,是一個(gè)并不明智的策略,不僅需要 clone 其他倉庫并完成應(yīng)用的運(yùn)行,還要保證其代碼的時(shí)效性。Modern.js 提供了更優(yōu)的的策略:
某些子應(yīng)用需要更新時(shí) 主應(yīng)用線上環(huán)境 需要開發(fā)的子應(yīng)用線下環(huán)境 不需要開發(fā)的子應(yīng)用上線 主應(yīng)用需要更新時(shí) 主應(yīng)用線下環(huán)境 所有子應(yīng)用線上環(huán)境
通過以上更優(yōu)的調(diào)試策略,可以保證開發(fā)者僅運(yùn)行自己的關(guān)注的應(yīng)用即可。那么如何達(dá)到這種更優(yōu)的,可以采用應(yīng)用列表的下發(fā)模式,框架運(yùn)行時(shí)加載下發(fā)的應(yīng)用列表,在開發(fā)主應(yīng)用時(shí)拉取線上的應(yīng)用列表,在開發(fā)某個(gè)子應(yīng)用時(shí)代理代理列表中的資源為子應(yīng)用的列表。
傳統(tǒng) Web 應(yīng)用支持微前端模式
通過微前端運(yùn)行時(shí)章節(jié)可以發(fā)現(xiàn)傳統(tǒng) Web 應(yīng)用與微前端應(yīng)用間進(jìn)行切換成本并不高,但需要研發(fā)關(guān)注應(yīng)用的路由的調(diào)度、應(yīng)用的生命周期導(dǎo)出、額外的構(gòu)建配置、應(yīng)用通信數(shù)據(jù)觸發(fā)視圖更新,微前端模式應(yīng)用和傳統(tǒng) Web 應(yīng)用間如何進(jìn)行切換都存在一定的學(xué)習(xí)和理解成本。
在 Modern.js 中作為上層框架集成了 Garfish,原生支持微前端應(yīng)用,可以通過簡單配置即可完成微前端應(yīng)用類型的轉(zhuǎn)換,幫助用戶快速搭建應(yīng)用基礎(chǔ)結(jié)構(gòu),以降低其學(xué)習(xí)成本,快速生成微前端應(yīng)用。
微前端應(yīng)用如何支持 SSR
微前端作為一種全新的架構(gòu)模式,其分治設(shè)計(jì)模式除了帶來的諸多優(yōu)點(diǎn)外,但與之對應(yīng)的是引入了新的問題,如何支持傳統(tǒng) Web 應(yīng)用提供的 SSR 能力,由于微前端采用了分治的開發(fā)模式,應(yīng)用拆分成了多個(gè)子應(yīng)用,那么需要實(shí)現(xiàn)整體應(yīng)用的 SSR 能力,則需要與具體的 Web 框架相結(jié)合,通過制定微前端應(yīng)用的加載規(guī)則,達(dá)到微前端應(yīng)用也能有效的實(shí)現(xiàn) SSR 能力。
Modern.js 作為 Garfish 的上層框架,提供更開箱即用的上層能力 ,并解決了以上微前端不同于傳統(tǒng) Web 應(yīng)用開發(fā)后帶來的弊端,文末有關(guān)于 Modern.js 的發(fā)布預(yù)告,可以了解并關(guān)注。
微前端的優(yōu)點(diǎn)
適用于大規(guī)模 Web 應(yīng)用的開發(fā) 更快的開發(fā)速度 支持迭代可開發(fā)和增強(qiáng)升級 拆解后的部分降低了開發(fā)者的理解成本 同時(shí)具備 UX 和 DX 的開發(fā)模式
微前端的缺點(diǎn)
復(fù)雜度從代碼轉(zhuǎn)向基礎(chǔ)設(shè)施 整個(gè)應(yīng)用的穩(wěn)定性和安全性變得更加不可控 具備一定的學(xué)習(xí)和了解成本 需要建立全面的微前端周邊設(shè)施,才能充分發(fā)揮其架構(gòu)的優(yōu)勢 調(diào)試工具 監(jiān)控系統(tǒng) 上層 Web 框架 部署平臺(tái)
何時(shí)使用微前端
大規(guī)模企業(yè)級 Web 應(yīng)用開發(fā) 跨團(tuán)隊(duì)及企業(yè)級應(yīng)用協(xié)作開發(fā) 長期收益高于短期收益 不同技術(shù)選型的項(xiàng)目 內(nèi)聚的單個(gè)產(chǎn)品中部分需要獨(dú)立發(fā)布、灰度等能力 微前端的目標(biāo)并非用于取代 iframe 應(yīng)用的來源必須可信 用戶體驗(yàn)要求更高
總結(jié)
微前端概念的出現(xiàn)是前端發(fā)展的必然階段,PC 互聯(lián)網(wǎng)轉(zhuǎn)向移動(dòng)互聯(lián)網(wǎng)時(shí)代時(shí),PC 的場景并未完全被消滅,反而轉(zhuǎn)向了衍生出了更多沉浸感更高、體驗(yàn)感更強(qiáng)的應(yīng)用,與之對應(yīng)的應(yīng)該是出現(xiàn)新的架構(gòu)模式來應(yīng)對這些應(yīng)用規(guī)模的增長。
微前端也并非銀彈,采用微前端后復(fù)雜度并未憑空消失,而是由代碼轉(zhuǎn)向了基礎(chǔ)設(shè)施,對架構(gòu)設(shè)計(jì)帶來了更大的挑戰(zhàn),并且在新的架構(gòu)下需要設(shè)計(jì)并提供更多的周邊工具和生態(tài)來助力這一新的研發(fā)模式。
本文更多的是從背景和設(shè)計(jì)層面講清楚微前端解決方案應(yīng)具備哪些能力,以及核心模塊的設(shè)計(jì)。每一部分并未包含過于詳細(xì)的細(xì)節(jié),如果想要了解「微前端運(yùn)行時(shí)」詳細(xì)設(shè)計(jì),可以通過 https://github.com/modern-js-dev/garfish 倉庫了解細(xì)節(jié)。
參考
如何設(shè)計(jì)微前端中的主子路由調(diào)度:https://mp.weixin.qq.com/s/TAXP7ipDdtb2Jb-L3QHszA 如何取巧實(shí)現(xiàn)一個(gè)沙箱:https://mp.weixin.qq.com/s/Mg3fU0WvZUQnlWHdxc-b5A 微服務(wù)架構(gòu)及其最重要的 10 個(gè)設(shè)計(jì)模式:https://www.infoq.cn/article/kdw69bdimlx6fsgz1bg3 single-spa:https://github.com/single-spa/single-spa
Modern.js 開源預(yù)告
Modern.js 和 Garfish 都是字節(jié)跳動(dòng) Web Infra 發(fā)起的「現(xiàn)代 Web 工程體系」開源項(xiàng)目,Modern.js 原生支持微前端,在 Garfish 基礎(chǔ)上提供了完整的微前端最佳實(shí)踐。
Modern.js 計(jì)劃在 10 月 14 號發(fā)布 1.0.0 版和上線文檔站,歡迎關(guān)注和參與。
- END -
?? 點(diǎn)擊 「閱讀原文」,查看 Garfish 倉庫
