細(xì)述字節(jié)的微前端體系
點(diǎn)擊上方關(guān)注 前端技術(shù)江湖,一起學(xué)習(xí),天天進(jìn)步
這篇文章大致是為了回答幾個問題:
遷移到微前端,我們到底要什么? 業(yè)界的「微前端」體系通常包括哪些部分? 貼近研發(fā)同學(xué)側(cè)的「微前端框架」通常用什么樣的技術(shù)? 我們團(tuán)隊的項(xiàng)目現(xiàn)在能上「微前端」嗎?需要做什么改造、到什么地步?
作者 zthxxx
github.com/zthxxx
(以下全文讀完大約 20 min)
微前端已經(jīng)不是一個新概念了,大家或多或少都聽說過接觸過,這里不再去做一堆定義,只是對目前業(yè)界做法的調(diào)研總結(jié) / 概覽,這篇文章面向的是還沒有在業(yè)務(wù)中使用過微前端的同學(xué)或團(tuán)隊,通過這篇概覽,可以簡單的建立對 「微前端」的整體認(rèn)知;
總的來說「微前端」這個概念從造出來到發(fā)展如今,還處于一個百花齊放(各做各的)的發(fā)展中,沒有形成統(tǒng)一的(市場占有高的)共識 / 標(biāo)準(zhǔn);各個大廠 / 社區(qū)對這個概念以及背后的技術(shù)都有不同定義,各自為戰(zhàn),造的輪子也一堆。
所以,我們要什么?
如下這是一個典型的微前端結(jié)構(gòu)例子,在一個 URL 訪問的頁面中,有一個主應(yīng)用(基座),多個共存的子應(yīng)用 A/B,子應(yīng)用 B 內(nèi)還有嵌套的子應(yīng)用 C;它們可由不同團(tuán)隊獨(dú)立開發(fā),各個應(yīng)用獨(dú)立上線、互不干擾。

「獨(dú)立上線」「互不干擾」
對我們來說重要的是 「獨(dú)立上線」「互不干擾」,在上線發(fā)布層面互不干擾,在應(yīng)用共存層面互不干擾;
這是我們目前最直觀需要微前端給我們帶來的能力,因?yàn)槲覀兠媾R的直接問題就是 “內(nèi)部有模塊要拆到不同團(tuán)隊開發(fā)了,怎么發(fā)布上線” (即 “把內(nèi)部一些大模塊獨(dú)立成子應(yīng)用”)。
先從 「上線發(fā)布層面互不干擾」 說起,比如 Main App 是 v1.0.1 的版本, Sub App A 已經(jīng)是 v2.1.0 的版本的,不同團(tuán)隊的人各自上線自己的應(yīng)用,各自發(fā)版節(jié)奏之間沒有影響;

當(dāng) Sub App A 升級到 v2.1.1 的時候,Main App 和 Sub App B 應(yīng)該完全不做任何改動 / 發(fā)布,線上頁面就是 Sub App A 這個區(qū)域就是新的。

再把這個 case 簡化成最小情況,就一個主應(yīng)用、一個子應(yīng)用,來看看 「獨(dú)立上線」 這件事。

在此之前,我們先聊聊在非微前端時,頁面加載是怎么操作的:
通常前端頁面應(yīng)用打包結(jié)果的入口就是一段 <script> 標(biāo)簽加載 js 文件,執(zhí)行后往某一個 dom 節(jié)點(diǎn)下掛載內(nèi)容,類似如下
<html>
<head>
...
</head>
<body>
<div id="root"></div>
<script src="http://cdn/entry/[email protected]"></script>
</body>
</html>
當(dāng)在頁面訪問不同路由 (url) 時,原本打包的 js 內(nèi)部會去異步加載對應(yīng)路由、組件的 chunk js,拿到代碼后再去渲染這個路由下的內(nèi)容 / 組件;
以 webpack 為例,是通過插入 <script> 標(biāo)簽來獲取其他 chunk js,每個 chunk js 中通過 jsonp 的方式來加載 (入口文件則是 IIFE)。
<html>
<head>
...
<script src="http://cdn/chunk/0.f3c200e0.async.js"></script>
<script src="http://cdn/chunk/1.5bb06b78.async.js"></script>
</head>
<body>
<div id="root"></div>
<script src="http://cdn/entry/[email protected]"></script>
</body>
</html>
// 0.f3c200e0.async.js
(window.webpackJsonp = window.webpackJsonp || []).push([chunkId], xxxChunk);
復(fù)制代碼;
那么換到微前端框架上,這個加載會有一點(diǎn)區(qū)別,具體來說,是在渲染某些區(qū)域的內(nèi)容時,從「加載自身 chunk」變成 「加載應(yīng)用入口」,加載器從 webpack 換成「微前端容器」;
以訪問 https://xxx-domiain/main-app/sub-route/xxxx 為例,簡化流程為:
主應(yīng)用匹配到 /main-app/sub-soute路由,渲染當(dāng)前路由內(nèi)容當(dāng)前路由內(nèi)容中有子應(yīng)用,則異步加載子應(yīng)用入口 子應(yīng)用匹配到 /sub-route/xxxx路由,在自己的區(qū)域內(nèi)渲染對應(yīng)路由內(nèi)容

回到 「獨(dú)立上線」 這個事情上,首先大家已經(jīng)知道了微前端框架實(shí)際上就是 「父應(yīng)用加載子應(yīng)用入口」,再簡單預(yù)設(shè)這個「入口」也就是一段 js (或 html),就如下圖結(jié)構(gòu),

那么我們還是有那么一堆問題;
怎么注入加載入口腳本,從哪兒加載,怎么控制版本? 在哪兒上線,怎么上線? 子應(yīng)用上線升級版本,怎么不讓主應(yīng)用重新打包? 如何選擇不同版本 上線 / 回滾 / 灰度? 如何查看現(xiàn)在所有子應(yīng)用的列表? 多個版本之間切換如何集成聯(lián)調(diào)? ...
微前端體系
這實(shí)際反映出的是,我們對「微前端」的需求,不只是一個「微前端框架」,更是需要一整個配套的「微前端體系」;

這是體系大致包含的內(nèi)容,上一節(jié)最后的幾個問題,可以由 「治理體系」「開發(fā)配套」 來回答,而通常大家在聊的 「微前端框架」 只是這個體系里面的 「運(yùn)行時容器」 這一部分;
治理體系
「治理體系」簡單看可以視為一個 上線管理平臺 + 上線發(fā)布流程;
在目前調(diào)研結(jié)果來看,微前端的落地使用一定需要配套這么個管理平臺,雖然說的這么絕對,它也就比非微前端時候的上線平臺多兩個功能:
應(yīng)用管理 - 能上線各種主應(yīng)用、子應(yīng)用不同版本,列出上線應(yīng)用不同版本的入口地址 依賴管理 - 明確管理父子應(yīng)用依賴關(guān)系,將子應(yīng)用入口地址注入父應(yīng)用
根據(jù)上一節(jié)「入口加載」提到的,子應(yīng)用的入口加載,就是是父應(yīng)用去加載一段 js url 地址 ,如:https://cdn/.../[email protected] ,那么子應(yīng)用的 上線 也就是更新這個 url 地址 (版本),每次上線是一個新的入口地址 (版本);
并且要做到 “子應(yīng)用上線升級版本,不讓主應(yīng)用重新打包” 還需要讓這個入口 url 是通過上線平臺 注入 到父應(yīng)用,而不是 hardcode 寫到父應(yīng)用的代碼中;這個注入的過程、注入哪些子應(yīng)用,都是在這個上線管理平臺中做的。
其他如「版本發(fā)布」「灰度方案」「私有化」部分都和一般前端上線部署平臺一致,不再贅述;
但凡有實(shí)際上線需求,這個治理體系就沒法缺,缺了就表示這個團(tuán)隊又得再做一個出來。
附:一些調(diào)研結(jié)果
| 加載容器 | 管理平臺 |
|---|---|
| Qiankun[1] (基于 single-spa) | OneX (阿里內(nèi)部-螞蟻金服) |
| MicroX (阿里內(nèi)部) | MicroX + CSKit (阿里內(nèi)部-阿里云智能) |
| icestark[2] | Iceworks (阿里內(nèi)部-飛冰) |
| alfajs[3] | Alfa (阿里內(nèi)部-阿里云控制臺) |
開發(fā)配套
文檔這部分特別重要,也不只是對微前端框架的介紹文檔,包括開發(fā)本身的文檔,以及剛才說的「上線發(fā)布流程」,什么流程和怎么操作都該有串聯(lián)起來的文檔。
構(gòu)建 / 發(fā)布 這些略有區(qū)別,表現(xiàn)為打包的入口可能變了,甚至變多個了;子應(yīng)用產(chǎn)物的格式也多了一些限制,比如需要多一些跟「加載入口」相關(guān)的文件。
多個父子應(yīng)用間集成聯(lián)調(diào)涉及到:
本地開發(fā)子應(yīng)用可脫離父應(yīng)用 獨(dú)立啟動開發(fā)調(diào)試 調(diào)試本地子應(yīng)用和父應(yīng)用接入,兩者都用本地啟動 線上 bug 復(fù)現(xiàn),需要調(diào)試子應(yīng)用和父應(yīng)用接入,其中一個本地啟動,另一個加載線上
微物料
「微物料」這一塊畫虛線是因?yàn)樗容^偏體系建設(shè)后期(也離我們(Aeolus)目前需求比較遠(yuǎn)),在前期微前端運(yùn)作剛起步階段,是不需要實(shí)現(xiàn)的;
并且「微物料」的出現(xiàn)本質(zhì)上是對微前端形態(tài)的一種轉(zhuǎn)換,把微前端從 「不同頁面級別應(yīng)用組合,no-bundle」 轉(zhuǎn)換到 「根據(jù)接口協(xié)議,可以直接加載遠(yuǎn)程的組件、函數(shù),no-bundle」,
直接模糊了 App / Page / Component (widget) / Function / Plugin 的邊界;在寫法上形如原生瀏覽器 esm import 以及 deno import (調(diào)用方式上也頗有一種后端 「遠(yuǎn)程過程調(diào)用」(RPC) 的感覺),直接拉低組件復(fù)用的門檻、遠(yuǎn)程加載的門檻,于是物料市場這種東西也順勢就會有;
// 偽代碼示例,加載函數(shù)級別的微組件并執(zhí)行
import foo from "https://cdn/.../foo.js";
const foo = import("https://cdn/.../foo.js");
const foo = load("https://cdn/.../foo.js");
foo(xxx);
復(fù)制代碼;
在這個場景下,簡單區(qū)分下目前這幾個稱呼的邊界
App - 一整個微前端應(yīng)用,內(nèi)部也能有很多模塊、多個頁面 (Page) Page - 一個稍大一點(diǎn)有路由的微前端組件可以稱為頁面,如一個數(shù)據(jù)查詢頁面 Widget - 沒有路由的小組件(掛件),如一個樣式很獨(dú)特的按鈕 Function - 被遠(yuǎn)程加載執(zhí)行的一個功能函數(shù),如試想一下用 UMD 加載 lodash 一個 func (接口格式定義在應(yīng)用外) Plugin - 接口格式、執(zhí)行上下文定義的比較嚴(yán)的函數(shù) (接口格式由應(yīng)用定義)
運(yùn)行時容器
這部分就是通常狹義上的「微前端框架」做的事;
它主要是要干什么呢,大概這些事:
應(yīng)用加載 - 根據(jù)注冊的子應(yīng)用,通過給定的 url,加載約定格式的子應(yīng)用入口,并掛載到給定位置 部分框架是根據(jù)類似 manifest 的數(shù)據(jù),來獲取子應(yīng)用注冊情況以及入口地址 部分框架支持和管理平臺配合,運(yùn)行時接受平臺動態(tài)注入的入口地址 (也有框架宣稱運(yùn)行時注入和管理平臺解耦,但實(shí)際是如果不用,就得自己實(shí)現(xiàn)注入邏輯) JS 做入口更純粹,用 HTML 做入口更易于舊項(xiàng)目改造 業(yè)界目前常用兩種入口格式, HTML 和 JS 父子入口組合(即確定依賴關(guān)系)也有兩種模式,構(gòu)建時組合 和 運(yùn)行時組合 生命周期 - 加載 / 掛載 / 更新 / 卸載 等 加載 / 掛載時做的初始化、權(quán)限守衛(wèi)、i18n 語言等 卸載時做清理,如卸載 script 標(biāo)簽、style 標(biāo)簽、子應(yīng)用 dom 等 以及路由、父子通信時做雙向更新的橋梁 路由同步 - 子應(yīng)用的路由切換時,同步更新 url;url 跳轉(zhuǎn) / 更新時,同步更新子應(yīng)用 也就是對子應(yīng)用做到路由等同于 url 應(yīng)用通信 - 是說支持父子應(yīng)用之間便捷地相互通信,不像 postMessage 那樣難用 (指字符串) 什么,你問兄弟應(yīng)用相互通信?當(dāng)然大家都是用父應(yīng)用作 EventHub 沙箱隔離 - 為了各個應(yīng)用「互補(bǔ)干擾」,需要把各個應(yīng)用在“隔離”的環(huán)境中執(zhí)行 缺少隔離的話,CSS 全局樣式可能 沖突混亂,JS 全局變量可能被 污染 / 篡改 / 替換 這一部分業(yè)界的方案和演進(jìn)比較多,下一章會展開講講 異常處理 - 以上所有東西在報錯時的統(tǒng)一處理,比如加載失敗、或者路由匹配失敗
沙箱隔離
通常在多個應(yīng)用間,需要做隔離的就兩個部分, JS & CSS;

JS 隔離
Snapshot
子應(yīng)用掛載時,先對全局 window 變量打個快照放閉包里,再把全局 window 丟給子應(yīng)用,并在子應(yīng)用卸載時通過快照恢復(fù)全局 window 變量;
這是早期部分框架的做法,實(shí)際上這也并沒有形成“隔離”,只是防止多個子應(yīng)用互相“污染”;限制也非常多:
父子子應(yīng)用不能共存,一個 url 路由下整個頁面都是某一子應(yīng)用 多個子應(yīng)用之間不能共存,因?yàn)槿?window 只有一個,快照只有一個 快照的方式安全性不夠嚴(yán)格,深拷貝遇到 document / history 等都會有問題
現(xiàn)在已經(jīng)沒有這么干的了,都是用沙箱的思路。
Sandbox
Wasm VM
重新編譯一個 Wasm 的 JS 解釋器放在瀏覽器中,把子應(yīng)用直接放進(jìn)這個 VM 中執(zhí)行;
隔離非常嚴(yán)格,看到過很多技術(shù)文章講解,但目前沒有調(diào)研到有實(shí)際微前端框架這么干的,
原因和大家不用 Web Worker / iframe 一樣,隔離太嚴(yán)格了,通信非常麻煩,通信開銷非常大;
(但在除微前端之外有一些用,比如 StackBlitz[4] )
with() + new Function(code) + Proxy
with 語法用于改變作用域鏈,這里用來攔截寫訪問全局變量時對 window 的查找,如直接訪問 Array.from 而不是 window.Array.from 寫法時;
new Function 執(zhí)行 code 作用等同于 eval,但 eval 能訪問到當(dāng)前局部作用域變量,new Function 返回函數(shù)不管哪里執(zhí)行,都只能訪問全局作用域,正是我們想要的。
而 Proxy 提供的是 with 和 new Function 閉包中用到的充當(dāng) window 作用域的對象,通過白名單屬性限制能訪問真正 window 上的部分元素,通過 Proxy 讓刪除 / 添加全局變量 / api 時不會對真正全局 window 產(chǎn)生影響;
同時對 document / history / localtion 上各類操作做劫持,比如把 document.body 上插入元素乾坤大挪移、把 history.push 改寫再同步到 url、把 localtion path 攔截讓子應(yīng)用只獲取內(nèi)部路由, 等等,這些種種限制組成沙箱環(huán)境;
// 簡化偽代碼示例
window = new Proxy(pick(window, whiteListProperties), { ... })
document = new Proxy(document, { ... })
...
sandbox = new Function(`
return function ({ window, location, history, document }, code){
with(window) {
${code}
}
}`)
sandbox().call(window, { window, location, history, document }, code)
但這里對 window 攔截的程度是有限的,甚至可以簡單理解為「淺拷貝」而非「深拷貝」,通過全局通用 API 很容易做到逃逸而實(shí)現(xiàn)污染,比如直接改掉 Array.prototype.push 的行為;
with() + new Function(code) + Proxy + iframe contex
為了更安全的解決上面的 Proxy window 全局 API 逃逸問題,可以取一個 iframe 的 window 作為沙箱環(huán)境上下文的 window;
這里的 iframe 并不是直接作為沙箱來執(zhí)行子應(yīng)用代碼,子應(yīng)用依然執(zhí)行在 with + new Function 中,這個 iframe 只是個創(chuàng)建出來的空的 same-origin iframe,唯一用途是取它的 iframe.contentWindow 對象傳給子應(yīng)用做 window;
因?yàn)?iframe 的嚴(yán)格隔離性,一切全局對象跟外層均沒有任何關(guān)系(除了 parent),因此內(nèi)外兩個 Array Array.prototype 都不相同,等同于把上一個方案的 window 攔截做到了 「深拷貝」,是一種目前比較完善優(yōu)雅的沙箱方案;
(對 document / history / localtion 的代理攔截與上一個方案無異)
// 簡化偽代碼示例
frame = document.body.appendChild(document.createElement('iframe',{
src: 'about:blank',
sandbox: "allow-scripts allow-same-origin allow-popups allow-presentation allow-top-navigation",
style: 'display: none;',
}))
window = new Proxy(frame.contentWindow, { ... })
document = new Proxy(document, { ... })
...
sandbox = new Function(`
return function ({ window, location, history, document }, code){
with(window) {
${code}
}
}`)
sandbox().call(window, { window, location, history, document }, code)
Realms
tc39 還在提案中的新規(guī)范 Realms[5],stage 2,可以創(chuàng)建完全獨(dú)立的全局對象和全局作用域,用來實(shí)現(xiàn)沙箱正合適 (也有部分關(guān)于逃逸的討論[6]),
目前沒有調(diào)研到任何微前端框架在用,僅 Figma 提到用于自身插件方案(上文提到「微物料」化之后,插件也能納入「微前端」范疇);
CSS 隔離
不同于 JS 隔離的相對成熟, CSS 隔離在業(yè)界完全不成熟,目前對于大部分微前端框架都是有點(diǎn)問題;
大部分都會告訴你,用工程化的方式加前綴來防止沖突,比如 BEM / css modules / css in js / 自定義前綴 等;
但這沒法解決不同應(yīng)用依賴了同一個 UI 庫不同版本的情況;
并且大部分歷史項(xiàng)目里面也有很多硬編碼的 className 很難徹底改造;
切換應(yīng)用時卸載
與上文提到的 JS 隔離用的 Snapshot 在應(yīng)用切換時的[掛載 / 卸載原理]相同,問題也相同,不再贅述;
Shadow DOM
Shadow DOM[7] 聽起來才是真正有效用于 CSS 隔離的沙箱,有著和 iframe 一樣嚴(yán)格的 DOM 隔離,Shadow DOM 內(nèi)部的元素始終不會影響到它外部的元素;
并且不管是 <style> 或 <link rel="stylesheet"> 產(chǎn)生的 css 在內(nèi)外之間都是互不影響;

(圖源 MDN)
聽起來很美好,只需要給每個子應(yīng)用外面套一個 Shadow DOM 就萬事大吉,子應(yīng)用往 head 里插入的 style / css link 都攔截到這個 Shadow DOM 內(nèi);
<html>
?? <head>...</head>
▼ <body>
▼ <div id="main-app" >
...
<!-- 子應(yīng)用 A 對應(yīng)的 shadow dom 容器 -->
▼ <div id="sub-app-a-container">
▼ #shadow-root (open)
?? <style>...</style>
?? <link rel="stylesheet">...</link>
▼ <div id="sub-app-a">
...
</div>
</div>
</div>
</body>
</html>
但實(shí)際上,除了兼容性、瀏覽器 Shadow DOM 有一堆 BUG、react-dom 低版本對 Shadow DOM 事件不支持外,還有一個問題:
彈窗遮罩
準(zhǔn)確的說是:子應(yīng)用那些通過 JS 往 document.body 上插的元素,如 Tooltip / Popover / Modal 怎么辦?
他們要是真插入到 document.body 上了,就跳過了 Shadow DOM,也就沒有了子應(yīng)用的 CSS,樣式就沒了啊;
要是被 JS 沙箱的 document 劫持到了插入操作,那這些 Tooltip / Popover / Modal 元素應(yīng)該插入到哪里?
如果是插入到子應(yīng)用 Shadow DOM 內(nèi)跟掛載 DOM 同級的位置,可能因?yàn)?DOM 結(jié)構(gòu)(順序)改變導(dǎo)致子應(yīng)用某些樣式出問題,也可能因?yàn)樽討?yīng)用所在區(qū)域的 位置、大小、margin/padding 跟 body 不一致,導(dǎo)致這個插入的元素(如 Tooltip)的定位出現(xiàn)偏差,畢竟不是所有插入元素都用 fixed 定位;

一種 hack 的解決辦法是,在 document.body 末尾給每個子應(yīng)用對應(yīng)再放一個 Shadow DOM 的 div,這個 div 和 document.body 的定位、大小、margin/padding 屬性都完全一樣,等同于覆蓋在 body 之上,并且內(nèi)部完全同步了對應(yīng)子應(yīng)用插入的 style / css link 標(biāo)簽,
這個 Shadow DOM 的 div 用來承載子應(yīng)用插入到 document.body 上的元素(需要 JS 沙箱配合),這樣,不管是 Tooltip / Popover / Modal 還是沒有 fixed 定位的元素,獲取到的 css 都和子應(yīng)用內(nèi)部一致,并且所在位置又和 body 對齊,基本解決問題;
<html>
?? <head>...</head>
▼ <body>
▼ <div id="main-app" >
...
<!-- 子應(yīng)用 A 對應(yīng)的 shadow dom 容器 -->
▼ <div id="sub-app-a-container">
▼ #shadow-root (open)
?? <style>...</style>
?? <link rel="stylesheet">...</link>
▼ <div id="sub-app-a">
...
</div>
</div>
</div>
<!-- 子應(yīng)用 A 同步所有樣式的 shadow dom 容器 -->
▼ <div id="sub-app-a-global-shadow">
▼ #shadow-root (open)
?? <style>...</style>
?? <link rel="stylesheet">...</link>
▼ <div id="modal">
...
</div>
</div>
</body>
</html>
但 hack 并不是完美的,但這里基于同步 css 的做法可能會有無法同步、遺漏,等問題;
比如對 <style>標(biāo)簽內(nèi)部的同步需要一直監(jiān)聽、兩個 Shadow DOM 之間需要來回同步,因?yàn)槿魏我粋€內(nèi)都可能插入新的<style>標(biāo)簽,也能在原有的某個<style>標(biāo)簽內(nèi)修改;再比如 css in js 方案為了性能通常會使用 CSSStyleSheet.insertRule\(\) API[8] 來創(chuàng)建樣式,這樣元素雖然能受到 css 樣式影響,但對應(yīng) <style>標(biāo)簽內(nèi)容是完全空的,基于標(biāo)簽內(nèi)容的手動無法同步,需要 JS 沙箱配合劫持 insertRule API 來做同步;以及如果子應(yīng)用通過 JS 插入 dom 的位置不是 document.body ,而是其他任何一個一有 dom 的位置,這里也很難做劫持;
技術(shù)債 !!
下面列出的是典型的技術(shù)債,不過它們清理的過程,也可以看做微前端改造過程的一部分。
模塊之間組件的交叉耦合
模塊內(nèi)引入了其他模塊的內(nèi)部組件 / 方法,
這些被引用項(xiàng)應(yīng)該拆分出去成公共組件 / 方法;
(如數(shù)據(jù)準(zhǔn)備用到標(biāo)簽的表達(dá)式樹組件、可視化篩選器組件等)
公共依賴組件/方法還沒完整拆分打包
Common 公共組件 / Service 公共方法等,需要后續(xù)重構(gòu)拆分發(fā)包
URL 路由模式改造還沒做 (hash history => browser history)
之前 Aeolus 一直有需求和計劃要從 hash history 改到 browser history,但還沒做,
如果計劃要改造,但在此之前做了微前端改造,那么之后路由改造的兼容可能更難做;
不同微前端容器對路由模式的支持程度不一樣,并且對父子應(yīng)用能否使用不同模式的支持也不一樣;
React v17 升級以修復(fù) Shadow DOM 問題
主流框架 CSS 隔離都有帶 Shadow DOM 支持,而 React 需要升級到 v17 才有對 Shadow DOM 各種問題的修復(fù);
又因?yàn)槟壳坝玫?umi, react 運(yùn)行版本是由 umi 內(nèi)部包控制的,所以實(shí)際上這個升級是連帶著 umi 一起升級的,新版 umi 的路由懶編譯等特性也需要處理。
硬編碼的寫在代碼中的 jsx className
大部分這類 className 都沒有前綴,命名也很簡單 (right, left, first, last ...),極易造成沖突,這部分也需要改造或重構(gòu);代碼里這部分有不少,還很難統(tǒng)計;
Dev / CI / CD 流程還在改造中 (monorepo / dev preformance / ci tasks)
而本身微前端的改造也對 Dev / CI / CD 流程有影響,因此兩者最好不要同時做;
公共依賴復(fù)用
微前端不解決復(fù)用問題,「依賴復(fù)用」本身不是微前端框架該做的事 ,有些依賴是不能也不應(yīng)該復(fù)用的(如代碼的執(zhí)行會對依賴本身的內(nèi)部變量/context 產(chǎn)生副作用)(部分框架做了 npm lib 級別的復(fù)用抽取,但也會導(dǎo)致 bundle chunk 有問題);
用哪個框架?
技術(shù)上,用什么框架都可以,各個框架的設(shè)計基本都是宣稱幾行輕量級無侵入的接入方式,因此接入成本和替換成本都很小;重點(diǎn)是風(fēng)神自己需要做完模塊拆分;
實(shí)際上,我們只能選有配套治理體系服務(wù)的、研發(fā)者離我們近的;否則我們需要自己根據(jù)框架造配套治理體系以及自行排查接入源碼級問題。
因此我們同時需要在改造時做好不耦合、甚至能隨時替換微前端框架的準(zhǔn)備。
要改造到什么程度?
內(nèi)部公共依賴該拆分的拆分,該發(fā)包的發(fā)包 對應(yīng)模塊完整移動到其他倉庫(或 monorepo 目錄,如 apps/ ),并能獨(dú)立啟動開發(fā) (因?yàn)槟荛_發(fā)就能做部署了) 剩下就是按照對應(yīng)框架文檔做打包接入 具體實(shí)際改造過程和過渡階段工程化有什么指導(dǎo)性方案?
Refs
把整個過程中,看的比較有關(guān)聯(lián)有價值的文章列出來了
起源
Techniques, strategies and recipes for building a modern web app with multiple teams using different[9]
綜述
【推薦】是對微前端的一些總體概覽,包括設(shè)計演進(jìn)、技術(shù)演進(jìn)等
[Live Record][10] 微前端如何設(shè)計落地 - InfoQ | Phodal[11] Micro-frontend Architecture in Action-微前端的那些事兒 | Phodal[12] 如何設(shè)計實(shí)現(xiàn)一個微前端框 QianKun - 方渙[13] 微前端到底是什么?- 前端向后[14] 你必須知道的 11 個微前端框架-InfoQ[15]
Webpack5 Module Federation
Module Federation | webpack[16] Webpack 5 Federation. A Game-changer to Javascript architecture. - inDepthDev[17] 精讀《Webpack5 新特性 - 模塊聯(lián)邦》[18] 三大應(yīng)用場景調(diào)研,Webpack 新功能 Module Federation 深入解析-阿里云開發(fā)者社區(qū)[19] webpack 打包的代碼怎么在瀏覽器跑起來的?看不懂算我輸[20]
沒有沙箱,只有代碼打包復(fù)用
Qiankun
qiankun 文檔官網(wǎng)[21] / umijs/qiankun Github[22] @umijs/plugin-qiankun[23] umijs/umi-plugin-qiankun examples[24] 目標(biāo)是最完善的微前端解決方案 - qiankun 2.0[25] 微前端的核心價值 · 語雀[26] 如何設(shè)計實(shí)現(xiàn)一個微前端框 QianKun - 方渙[27] 飛豬微前端實(shí)踐:統(tǒng)一運(yùn)營工作臺的解決方案[28] 阿里云開放平臺微前端方案的沙箱實(shí)現(xiàn)[29] Click event not firing when React Component in a Shadow DOM[30]
qiankun 是運(yùn)行時容器、加載器,但沒有解答的工程與平臺問題
Magic microservices
github.com/bytedance/m…[31]
理念是 web components 做隔離,是純框架容器的一層,不包含管理平臺
Puzzle
github.com/puzzle-js/p…[32]
Bit.dev
Bit: The platform for the modular web[33] teambit/bit[34] Installing Bit | Documentation[35]
Alfa
Alfa | Alibaba Cloud Alfa[36] github.com/aliyun/alib…[37] 如何“取巧”實(shí)現(xiàn)一個微前端沙箱?-阿里云開發(fā)者社區(qū)[38]
沙盒隔離
談?wù)勎⑶岸祟I(lǐng)域的 js 沙箱實(shí)現(xiàn)機(jī)制 - 騰訊大講堂[39] 如何“取巧”實(shí)現(xiàn)一個微前端沙箱?- 阿里云 Browser VM[40] 淺探 Web Worker 與 JavaScript 沙箱[41] alibabacloud-alfa/browser-vm/src/Context.js | Browser VM 沙箱實(shí)現(xiàn)核心代碼[42]
Figma
How to build a plugin system on the web and also sleep well at night[43] How Plugins Run · Figma Developers[44] github.com/tc39/propos…[45]
Figma 博客詳細(xì)講了它們插件系統(tǒng)的沙箱隔離是怎么一步步演進(jìn)的;Figma 使用 Realms[46] 和 same-origin iframe + null-origin iframe 為沙箱中代碼創(chuàng)建上下文環(huán)境。
參考資料
https://qiankun.umijs.org: https://link.juejin.cn/?target=https%3A%2F%2Fqiankun.umijs.org
[2]https://micro-frontends.ice.work: https://link.juejin.cn/?target=https%3A%2F%2Fmicro-frontends.ice.work
[3]https://alfajs.io: https://link.juejin.cn/?target=https%3A%2F%2Falfajs.io
[4]https://blog.stackblitz.com/posts/introducing-webcontainers: https://link.juejin.cn/?target=https%3A%2F%2Fblog.stackblitz.com%2Fposts%2Fintroducing-webcontainers
The End
歡迎自薦投稿到《前端技術(shù)江湖》,如果你覺得這篇內(nèi)容對你挺有啟發(fā),記得點(diǎn)個 「在看」哦
點(diǎn)個『在看』支持下 
