<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從場(chǎng)景倒推,在字節(jié)我們要什么樣的微前端體系

          共 23067字,需瀏覽 47分鐘

           ·

          2021-07-20 16:02

          點(diǎn)擊上方 前端Q,關(guān)注公眾號(hào)

          回復(fù)加群,加入前端Q技術(shù)交流群



          這篇文章大致是為了回答幾個(gè)問題:

          1. 遷移到微前端,我們到底要什么?
          2. 業(yè)界的「微前端」體系通常包括哪些部分?
          3. 貼近研發(fā)同學(xué)側(cè)的「微前端框架」通常用什么樣的技術(shù)?
          4. 我們團(tuán)隊(duì)的項(xiàng)目現(xiàn)在能上「微前端」嗎?需要做什么改造、到什么地步?

          作者 zthxxx

          github.com/zthxxx

          (以下全文讀完大約 20 min)

          微前端已經(jīng)不是一個(gè)新概念了,大家或多或少都聽說過接觸過,這里不再去做一堆定義,只是對(duì)目前業(yè)界做法的調(diào)研總結(jié) / 概覽,這篇文章面向的是還沒有在業(yè)務(wù)中使用過微前端的同學(xué)或團(tuán)隊(duì),通過這篇概覽,可以簡(jiǎn)單的建立對(duì) 「微前端」的整體認(rèn)知;

          總的來說「微前端」這個(gè)概念從造出來到發(fā)展如今,還處于一個(gè)百花齊放(各做各的)的發(fā)展中,沒有形成統(tǒng)一的(市場(chǎng)占有高的)共識(shí) / 標(biāo)準(zhǔn);各個(gè)大廠 / 社區(qū)對(duì)這個(gè)概念以及背后的技術(shù)都有不同定義,各自為戰(zhàn),造的輪子也一堆。

          所以,我們要什么?

          如下這是一個(gè)典型的微前端結(jié)構(gòu)例子,在一個(gè) URL 訪問的頁面中,有一個(gè)主應(yīng)用(基座),多個(gè)共存的子應(yīng)用 A/B,子應(yīng)用 B 內(nèi)還有嵌套的子應(yīng)用 C;它們可由不同團(tuán)隊(duì)獨(dú)立開發(fā),各個(gè)應(yīng)用獨(dú)立上線、互不干擾

          「獨(dú)立上線」「互不干擾」

          對(duì)我們來說重要的是 「獨(dú)立上線」「互不干擾」,在上線發(fā)布層面互不干擾,在應(yīng)用共存層面互不干擾;

          這是我們目前最直觀需要微前端給我們帶來的能力,因?yàn)槲覀兠媾R的直接問題就是 “內(nèi)部有模塊要拆到不同團(tuán)隊(duì)開發(fā)了,怎么發(fā)布上線” (即 “把內(nèi)部一些大模塊獨(dú)立成子應(yīng)用”)。

          先從 「上線發(fā)布層面互不干擾」 說起,比如 Main App 是 v1.0.1 的版本, Sub App A 已經(jīng)是 v2.1.0 的版本的,不同團(tuán)隊(duì)的人各自上線自己的應(yīng)用,各自發(fā)版節(jié)奏之間沒有影響;

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

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

          在此之前,我們先聊聊在非微前端時(shí),頁面加載是怎么操作的:

          通常前端頁面應(yīng)用打包結(jié)果的入口就是一段 <script> 標(biāo)簽加載 js 文件,執(zhí)行后往某一個(gè) 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) 時(shí),原本打包的 js 內(nèi)部會(huì)去異步加載對(duì)應(yīng)路由、組件的 chunk js,拿到代碼后再去渲染這個(gè)路由下的內(nèi)容 / 組件;

          以 webpack 為例,是通過插入 <script> 標(biāo)簽來獲取其他 chunk js,每個(gè) 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ù)制代碼;

          那么換到微前端框架上,這個(gè)加載會(huì)有一點(diǎn)區(qū)別,具體來說,是在渲染某些區(qū)域的內(nèi)容時(shí),從「加載自身 chunk」變成 「加載應(yīng)用入口」,加載器從 webpack 換成「微前端容器」;

          以訪問 https://xxx-domiain/main-app/sub-route/xxxx 為例,簡(jiǎn)化流程為:

          1. 主應(yīng)用匹配到 /main-app/sub-soute 路由,渲染當(dāng)前路由內(nèi)容
          2. 當(dāng)前路由內(nèi)容中有子應(yīng)用,則異步加載子應(yīng)用入口
          3. 子應(yīng)用匹配到 /sub-route/xxxx 路由,在自己的區(qū)域內(nèi)渲染對(duì)應(yīng)路由內(nèi)容

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

          那么我們還是有那么一堆問題;

          • 怎么注入加載入口腳本,從哪兒加載,怎么控制版本?
          • 在哪兒上線,怎么上線?
          • 子應(yīng)用上線升級(jí)版本,怎么不讓主應(yīng)用重新打包?
          • 如何選擇不同版本 上線 / 回滾 / 灰度?
          • 如何查看現(xiàn)在所有子應(yīng)用的列表?
          • 多個(gè)版本之間切換如何集成聯(lián)調(diào)?
          • ...

          微前端體系

          這實(shí)際反映出的是,我們對(duì)「微前端」的需求,不只是一個(gè)「微前端框架」,更是需要一整個(gè)配套的「微前端體系」;

          這是體系大致包含的內(nèi)容,上一節(jié)最后的幾個(gè)問題,可以由 「治理體系」「開發(fā)配套」 來回答,而通常大家在聊的 「微前端框架」 只是這個(gè)體系里面的 「運(yùn)行時(shí)容器」 這一部分;

          治理體系

          「治理體系」簡(jiǎn)單看可以視為一個(gè) 上線管理平臺(tái) + 上線發(fā)布流程;

          在目前調(diào)研結(jié)果來看,微前端的落地使用一定需要配套這么個(gè)管理平臺(tái),雖然說的這么絕對(duì),它也就比非微前端時(shí)候的上線平臺(tái)多兩個(gè)功能:

          • 應(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)用的 上線 也就是更新這個(gè) url 地址 (版本),每次上線是一個(gè)新的入口地址 (版本);

          并且要做到 “子應(yīng)用上線升級(jí)版本,不讓主應(yīng)用重新打包” 還需要讓這個(gè)入口 url 是通過上線平臺(tái) 注入 到父應(yīng)用,而不是 hardcode 寫到父應(yīng)用的代碼中;這個(gè)注入的過程、注入哪些子應(yīng)用,都是在這個(gè)上線管理平臺(tái)中做的。

          其他如「版本發(fā)布」「灰度方案」「私有化」部分都和一般前端上線部署平臺(tái)一致,不再贅述;

          但凡有實(shí)際上線需求,這個(gè)治理體系就沒法缺,缺了就表示這個(gè)團(tuán)隊(duì)又得再做一個(gè)出來。

          附:一些調(diào)研結(jié)果

          加載容器管理平臺(tái)
          Qiankun[1] (基于 single-spa)OneX (阿里內(nèi)部-螞蟻金服)
          MicroX (阿里內(nèi)部)MicroX + CSKit (阿里內(nèi)部-阿里云智能)
          icestark[2]Iceworks (阿里內(nèi)部-飛冰)
          alfajs[3]Alfa (阿里內(nèi)部-阿里云控制臺(tái))

          開發(fā)配套

          文檔這部分特別重要,也不只是對(duì)微前端框架的介紹文檔,包括開發(fā)本身的文檔,以及剛才說的「上線發(fā)布流程」,什么流程和怎么操作都該有串聯(lián)起來的文檔。

          構(gòu)建 / 發(fā)布 這些略有區(qū)別,表現(xiàn)為打包的入口可能變了,甚至變多個(gè)了;子應(yīng)用產(chǎn)物的格式也多了一些限制,比如需要多一些跟「加載入口」相關(guān)的文件。

          多個(gè)父子應(yīng)用間集成聯(lián)調(diào)涉及到:

          • 本地開發(fā)子應(yīng)用可脫離父應(yīng)用 獨(dú)立啟動(dòng)開發(fā)調(diào)試
          • 調(diào)試本地子應(yīng)用和父應(yīng)用接入,兩者都用本地啟動(dòng)
          • 線上 bug 復(fù)現(xiàn),需要調(diào)試子應(yīng)用和父應(yīng)用接入,其中一個(gè)本地啟動(dòng),另一個(gè)加載線上

          微物料

          「微物料」這一塊畫虛線是因?yàn)樗容^偏體系建設(shè)后期(也離我們(Aeolus)目前需求比較遠(yuǎn)),在前期微前端運(yùn)作剛起步階段,是不需要實(shí)現(xiàn)的;

          并且「微物料」的出現(xiàn)本質(zhì)上是對(duì)微前端形態(tài)的一種轉(zhuǎn)換,把微前端從 「不同頁面級(jí)別應(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)程加載的門檻,于是物料市場(chǎng)這種東西也順勢(shì)就會(huì)有;

          // 偽代碼示例,加載函數(shù)級(jí)別的微組件并執(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ù)制代碼;

          在這個(gè)場(chǎng)景下,簡(jiǎn)單區(qū)分下目前這幾個(gè)稱呼的邊界

          • App - 一整個(gè)微前端應(yīng)用,內(nèi)部也能有很多模塊、多個(gè)頁面 (Page)
          • Page - 一個(gè)稍大一點(diǎn)有路由的微前端組件可以稱為頁面,如一個(gè)數(shù)據(jù)查詢頁面
          • Widget - 沒有路由的小組件(掛件),如一個(gè)樣式很獨(dú)特的按鈕
          • Function - 被遠(yuǎn)程加載執(zhí)行的一個(gè)功能函數(shù),如試想一下用 UMD 加載 lodash 一個(gè) func (接口格式定義在應(yīng)用外)
          • Plugin - 接口格式、執(zhí)行上下文定義的比較嚴(yán)的函數(shù) (接口格式由應(yīng)用定義)

          運(yùn)行時(shí)容器

          這部分就是通常狹義上的「微前端框架」做的事;

          它主要是要干什么呢,大概這些事:

          • 應(yīng)用加載 - 根據(jù)注冊(cè)的子應(yīng)用,通過給定的 url,加載約定格式的子應(yīng)用入口,并掛載到給定位置
            • 部分框架是根據(jù)類似 manifest 的數(shù)據(jù),來獲取子應(yīng)用注冊(cè)情況以及入口地址
            • 部分框架支持和管理平臺(tái)配合,運(yùn)行時(shí)接受平臺(tái)動(dòng)態(tài)注入的入口地址 (也有框架宣稱運(yùn)行時(shí)注入和管理平臺(tái)解耦,但實(shí)際是如果不用,就得自己實(shí)現(xiàn)注入邏輯)
            • JS 做入口更純粹,用 HTML 做入口更易于舊項(xiàng)目改造
            • 業(yè)界目前常用兩種入口格式, HTMLJS
            • 父子入口組合(即確定依賴關(guān)系)也有兩種模式,構(gòu)建時(shí)組合運(yùn)行時(shí)組合
          • 生命周期 - 加載 / 掛載 / 更新 / 卸載 等
            • 加載 / 掛載時(shí)做的初始化、權(quán)限守衛(wèi)、i18n 語言等
            • 卸載時(shí)做清理,如卸載 script 標(biāo)簽、style 標(biāo)簽、子應(yīng)用 dom 等
            • 以及路由、父子通信時(shí)做雙向更新的橋梁
          • 路由同步 - 子應(yīng)用的路由切換時(shí),同步更新 url;url 跳轉(zhuǎn) / 更新時(shí),同步更新子應(yīng)用
            • 也就是對(duì)子應(yīng)用做到路由等同于 url
          • 應(yīng)用通信 - 是說支持父子應(yīng)用之間便捷地相互通信,不像 postMessage 那樣難用 (指字符串)
            • 什么,你問兄弟應(yīng)用相互通信?當(dāng)然大家都是用父應(yīng)用作 EventHub
          • 沙箱隔離 - 為了各個(gè)應(yīng)用「互補(bǔ)干擾」,需要把各個(gè)應(yīng)用在“隔離”的環(huán)境中執(zhí)行
            • 缺少隔離的話,CSS 全局樣式可能 沖突混亂,JS 全局變量可能被 污染 / 篡改 / 替換
            • 這一部分業(yè)界的方案和演進(jìn)比較多,下一章會(huì)展開講講
          • 異常處理 - 以上所有東西在報(bào)錯(cuò)時(shí)的統(tǒng)一處理,比如加載失敗、或者路由匹配失敗

          沙箱隔離

          通常在多個(gè)應(yīng)用間,需要做隔離的就兩個(gè)部分, JS & CSS;

          JS 隔離

          Snapshot

          子應(yīng)用掛載時(shí),先對(duì)全局 window 變量打個(gè)快照放閉包里,再把全局 window 丟給子應(yīng)用,并在子應(yīng)用卸載時(shí)通過快照恢復(fù)全局 window 變量;

          這是早期部分框架的做法,實(shí)際上這也并沒有形成“隔離”,只是防止多個(gè)子應(yīng)用互相“污染”;限制也非常多:

          • 父子子應(yīng)用不能共存,一個(gè) url 路由下整個(gè)頁面都是某一子應(yīng)用
          • 多個(gè)子應(yīng)用之間不能共存,因?yàn)槿?window 只有一個(gè),快照只有一個(gè)
          • 快照的方式安全性不夠嚴(yán)格,深拷貝遇到 document / history 等都會(huì)有問題

          現(xiàn)在已經(jīng)沒有這么干的了,都是用沙箱的思路。

          Sandbox

          Wasm VM

          重新編譯一個(gè) Wasm 的 JS 解釋器放在瀏覽器中,把子應(yīng)用直接放進(jìn)這個(gè) VM 中執(zhí)行;

          隔離非常嚴(yán)格,看到過很多技術(shù)文章講解,但目前沒有調(diào)研到有實(shí)際微前端框架這么干的,

          原因和大家不用 Web Worker / iframe 一樣,隔離太嚴(yán)格了,通信非常麻煩,通信開銷非常大;

          (但在除微前端之外有一些用,比如 StackBlitz[4] )

          with() + new Function(code) + Proxy

          with 語法用于改變作用域鏈,這里用來攔截寫訪問全局變量時(shí)對(duì) window 的查找,如直接訪問 Array.from 而不是 window.Array.from 寫法時(shí);

          new Function 執(zhí)行 code 作用等同于 eval,但 eval 能訪問到當(dāng)前局部作用域變量,new Function 返回函數(shù)不管哪里執(zhí)行,都只能訪問全局作用域,正是我們想要的。

          Proxy 提供的是 with 和 new Function 閉包中用到的充當(dāng) window 作用域的對(duì)象,通過白名單屬性限制能訪問真正 window 上的部分元素,通過 Proxy 讓刪除 / 添加全局變量 / api 時(shí)不會(huì)對(duì)真正全局 window 產(chǎn)生影響;

          同時(shí)對(duì) document / history / localtion 上各類操作做劫持,比如把 document.body 上插入元素乾坤大挪移、把 history.push 改寫再同步到 url、把 localtion path 攔截讓子應(yīng)用只獲取內(nèi)部路由, 等等,這些種種限制組成沙箱環(huán)境;

          // 簡(jiǎ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)

          但這里對(duì) window 攔截的程度是有限的,甚至可以簡(jiǎn)單理解為「淺拷貝」而非「深拷貝」,通過全局通用 API 很容易做到逃逸而實(shí)現(xiàn)污染,比如直接改掉 Array.prototype.push 的行為;

          with() + new Function(code) + Proxy + iframe contex

          為了更安全的解決上面的 Proxy window 全局 API 逃逸問題,可以取一個(gè) iframe 的 window 作為沙箱環(huán)境上下文的 window;

          這里的 iframe 并不是直接作為沙箱來執(zhí)行子應(yīng)用代碼,子應(yīng)用依然執(zhí)行在 with + new Function 中,這個(gè) iframe 只是個(gè)創(chuàng)建出來的空的 same-origin iframe,唯一用途是取它的 iframe.contentWindow 對(duì)象傳給子應(yīng)用做 window;

          因?yàn)?iframe 的嚴(yán)格隔離性,一切全局對(duì)象跟外層均沒有任何關(guān)系(除了 parent),因此內(nèi)外兩個(gè) Array Array.prototype 都不相同,等同于把上一個(gè)方案的 window 攔截做到了 「深拷貝」,是一種目前比較完善優(yōu)雅的沙箱方案;

          (對(duì) document / history / localtion 的代理攔截與上一個(gè)方案無異)

          // 簡(jiǎn)化偽代碼示例
          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ú)立的全局對(duì)象和全局作用域,用來實(shí)現(xiàn)沙箱正合適 (也有部分關(guān)于逃逸的討論[6]),

          目前沒有調(diào)研到任何微前端框架在用,僅 Figma 提到用于自身插件方案(上文提到「微物料」化之后,插件也能納入「微前端」范疇);

          CSS 隔離

          不同于 JS 隔離的相對(duì)成熟, CSS 隔離在業(yè)界完全不成熟,目前對(duì)于大部分微前端框架都是有點(diǎn)問題;

          大部分都會(huì)告訴你,用工程化的方式加前綴來防止沖突,比如 BEM / css modules / css in js / 自定義前綴 等;

          但這沒法解決不同應(yīng)用依賴了同一個(gè) UI 庫不同版本的情況;

          并且大部分歷史項(xiàng)目里面也有很多硬編碼的 className 很難徹底改造;

          切換應(yīng)用時(shí)卸載

          與上文提到的 JS 隔離用的 Snapshot 在應(yīng)用切換時(shí)的[掛載 / 卸載原理]相同,問題也相同,不再贅述;

          Shadow DOM

          Shadow DOM[7] 聽起來才是真正有效用于 CSS 隔離的沙箱,有著和 iframe 一樣嚴(yán)格的 DOM 隔離,Shadow DOM 內(nèi)部的元素始終不會(huì)影響到它外部的元素;

          并且不管是 <style><link rel="stylesheet"> 產(chǎn)生的 css 在內(nèi)外之間都是互不影響;

          (圖源 MDN)

          聽起來很美好,只需要給每個(gè)子應(yīng)用外面套一個(gè) Shadow DOM 就萬事大吉,子應(yīng)用往 head 里插入的 style / css link 都攔截到這個(gè) Shadow DOM 內(nèi);

          <html>
            ?? <head>...</head>
            ▼ <body>
              ▼ <div id="main-app" >
                ...

                <!-- 子應(yīng)用 A 對(duì)應(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 低版本對(duì) Shadow DOM 事件不支持外,還有一個(gè)問題:

          彈窗遮罩

          準(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 同級(jí)的位置,可能因?yàn)?DOM 結(jié)構(gòu)(順序)改變導(dǎo)致子應(yīng)用某些樣式出問題,也可能因?yàn)樽討?yīng)用所在區(qū)域的 位置、大小、margin/padding 跟 body 不一致,導(dǎo)致這個(gè)插入的元素(如 Tooltip)的定位出現(xiàn)偏差,畢竟不是所有插入元素都用 fixed 定位;

          一種 hack 的解決辦法是,在 document.body 末尾給每個(gè)子應(yīng)用對(duì)應(yīng)再放一個(gè) Shadow DOM 的 div,這個(gè) div 和 document.body 的定位、大小、margin/padding 屬性都完全一樣,等同于覆蓋在 body 之上,并且內(nèi)部完全同步了對(duì)應(yīng)子應(yīng)用插入的 style / css link 標(biāo)簽,

          這個(gè) Shadow DOM 的 div 用來承載子應(yīng)用插入到 document.body 上的元素(需要 JS 沙箱配合),這樣,不管是 Tooltip / Popover / Modal 還是沒有 fixed 定位的元素,獲取到的 css 都和子應(yīng)用內(nèi)部一致,并且所在位置又和 body 對(duì)齊,基本解決問題;

          <html>
            ?? <head>...</head>
            ▼ <body>
              ▼ <div id="main-app" >
                ...

                <!-- 子應(yīng)用 A 對(duì)應(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 的做法可能會(huì)有無法同步、遺漏,等問題;

          • 比如對(duì) <style> 標(biāo)簽內(nèi)部的同步需要一直監(jiān)聽、兩個(gè) Shadow DOM 之間需要來回同步,因?yàn)槿魏我粋€(gè)內(nèi)都可能插入新的 <style> 標(biāo)簽,也能在原有的某個(gè) <style>標(biāo)簽內(nèi)修改;
          • 再比如 css in js 方案為了性能通常會(huì)使用 CSSStyleSheet.insertRule\(\) API[8] 來創(chuàng)建樣式,這樣元素雖然能受到 css 樣式影響,但對(duì)應(yīng) <style> 標(biāo)簽內(nèi)容是完全空的,基于標(biāo)簽內(nèi)容的手動(dòng)無法同步,需要 JS 沙箱配合劫持 insertRule API 來做同步;
          • 以及如果子應(yīng)用通過 JS 插入 dom 的位置不是 document.body ,而是其他任何一個(gè)一有 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 一直有需求和計(jì)劃要從 hash history 改到 browser history,但還沒做,

            如果計(jì)劃要改造,但在此之前做了微前端改造,那么之后路由改造的兼容可能更難做;

            不同微前端容器對(duì)路由模式的支持程度不一樣,并且對(duì)父子應(yīng)用能否使用不同模式的支持也不一樣;

          • React v17 升級(jí)以修復(fù) Shadow DOM 問題

            主流框架 CSS 隔離都有帶 Shadow DOM 支持,而 React 需要升級(jí)到 v17 才有對(duì) Shadow DOM 各種問題的修復(fù);

            又因?yàn)槟壳坝玫?umi, react 運(yùn)行版本是由 umi 內(nèi)部包控制的,所以實(shí)際上這個(gè)升級(jí)是連帶著 umi 一起升級(jí)的,新版 umi 的路由懶編譯等特性也需要處理。

          • 硬編碼的寫在代碼中的 jsx className

            大部分這類 className 都沒有前綴,命名也很簡(jiǎn)單 (right, left, first, last ...),極易造成沖突,這部分也需要改造或重構(gòu);代碼里這部分有不少,還很難統(tǒng)計(jì);

          • Dev / CI / CD 流程還在改造中 (monorepo / dev preformance / ci tasks)

            而本身微前端的改造也對(duì) Dev / CI / CD 流程有影響,因此兩者最好不要同時(shí)做;

          公共依賴復(fù)用

          微前端不解決復(fù)用問題,「依賴復(fù)用」本身不是微前端框架該做的事 ,有些依賴是不能也不應(yīng)該復(fù)用的(如代碼的執(zhí)行會(huì)對(duì)依賴本身的內(nèi)部變量/context 產(chǎn)生副作用)(部分框架做了 npm lib 級(jí)別的復(fù)用抽取,但也會(huì)導(dǎo)致 bundle chunk 有問題);

          用哪個(gè)框架?

          技術(shù)上,用什么框架都可以,各個(gè)框架的設(shè)計(jì)基本都是宣稱幾行輕量級(jí)無侵入的接入方式,因此接入成本和替換成本都很小;重點(diǎn)是風(fēng)神自己需要做完模塊拆分;

          實(shí)際上,我們只能選有配套治理體系服務(wù)的、研發(fā)者離我們近的;否則我們需要自己根據(jù)框架造配套治理體系以及自行排查接入源碼級(jí)問題。

          因此我們同時(shí)需要在改造時(shí)做好不耦合、甚至能隨時(shí)替換微前端框架的準(zhǔn)備。

          要改造到什么程度?

          • 內(nèi)部公共依賴該拆分的拆分,該發(fā)包的發(fā)包
          • 對(duì)應(yīng)模塊完整移動(dòng)到其他倉庫(或 monorepo 目錄,如 apps/ ),并能獨(dú)立啟動(dòng)開發(fā) (因?yàn)槟荛_發(fā)就能做部署了)
          • 剩下就是按照對(duì)應(yīng)框架文檔做打包接入
          • 具體實(shí)際改造過程和過渡階段工程化有什么指導(dǎo)性方案?

          Refs

          把整個(gè)過程中,看的比較有關(guān)聯(lián)有價(jià)值的文章列出來了

          起源

          Techniques, strategies and recipes for building a modern web app with multiple teams using different[9]

          綜述

          【推薦】是對(duì)微前端的一些總體概覽,包括設(shè)計(jì)演進(jìn)、技術(shù)演進(jìn)等

          • [Live Record][10]
          • 微前端如何設(shè)計(jì)落地 - InfoQ | Phodal[11]
          • Micro-frontend Architecture in Action-微前端的那些事兒 | Phodal[12]
          • 如何設(shè)計(jì)實(shí)現(xiàn)一個(gè)微前端框 QianKun - 方渙[13]
          • 微前端到底是什么?- 前端向后[14]
          • 你必須知道的 11 個(gè)微前端框架-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)用場(chǎ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]
          • 微前端的核心價(jià)值 · 語雀[26]
          • 如何設(shè)計(jì)實(shí)現(xiàn)一個(gè)微前端框 QianKun - 方渙[27]
          • 飛豬微前端實(shí)踐:統(tǒng)一運(yùn)營工作臺(tái)的解決方案[28]
          • 阿里云開放平臺(tái)微前端方案的沙箱實(shí)現(xiàn)[29]
          • Click event not firing when React Component in a Shadow DOM[30]

          qiankun 是運(yùn)行時(shí)容器、加載器,但沒有解答的工程與平臺(tái)問題

          Magic microservices

          • github.com/bytedance/m…[31]

          理念是 web components 做隔離,是純框架容器的一層,不包含管理平臺(tái)

          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)一個(gè)微前端沙箱?-阿里云開發(fā)者社區(qū)[38]

          沙盒隔離

          • 談?wù)勎⑶岸祟I(lǐng)域的 js 沙箱實(shí)現(xiàn)機(jī)制 - 騰訊大講堂[39]
          • 如何“取巧”實(shí)現(xiàn)一個(gè)微前端沙箱?- 阿里云 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)境。


          內(nèi)推社群


          我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對(duì)加入騰訊感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


          參考資料

          [1]

          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

          [5]

          https://github.com/tc39/proposal-realms/blob/main/explainer.md: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftc39%2Fproposal-realms%2Fblob%2Fmain%2Fexplainer.md

          [6]

          https://github.com/tc39/proposal-realms/issues/277: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftc39%2Fproposal-realms%2Fissues%2F277

          [7]

          https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FWeb_Components%2FUsing_shadow_DOM

          [8]

          https://developer.mozilla.org/zh-CN/docs/Web/API/CSSStyleSheet/insertRule: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FCSSStyleSheet%2FinsertRule

          [9]

          https://micro-frontends.org/: https://link.juejin.cn/?target=https%3A%2F%2Fmicro-frontends.org%2F

          [10]

          https://www.bilibili.com/video/BV1Cf4y1x7Gu: https://link.juejin.cn/?target=https%3A%2F%2Fwww.bilibili.com%2Fvideo%2FBV1Cf4y1x7Gu

          [11]

          https://www.infoq.cn/article/xm_aaiotxmlppgwvx9y9: https://link.juejin.cn/?target=https%3A%2F%2Fwww.infoq.cn%2Farticle%2Fxm_aaiotxmlppgwvx9y9

          [12]

          https://microfrontends.cn/: https://link.juejin.cn/?target=https%3A%2F%2Fmicrofrontends.cn%2F

          [13]

          https://juejin.cn/post/6846687602439897101: https://juejin.cn/post/6846687602439897101

          [14]

          https://zhuanlan.zhihu.com/p/96464401: https://link.juejin.cn/?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F96464401

          [15]

          https://www.infoq.cn/article/22CIyQBs3S0bHeKVNORP: https://link.juejin.cn/?target=https%3A%2F%2Fwww.infoq.cn%2Farticle%2F22CIyQBs3S0bHeKVNORP

          [16]

          https://webpack.js.org/concepts/module-federation/: https://link.juejin.cn/?target=https%3A%2F%2Fwebpack.js.org%2Fconcepts%2Fmodule-federation%2F

          [17]

          https://indepth.dev/posts/1173/webpack-5-module-federation-a-game-changer-in-javascript-architecture#its-important-to-note-these-are-special-entry-points-they-are-only-a-few-kb-in-size-containing-a-special-webpack-runtime-that-can-interface-with-the-host-it-is-not-a-standard-entry-point--7/: https://link.juejin.cn/?target=https%3A%2F%2Findepth.dev%2Fposts%2F1173%2Fwebpack-5-module-federation-a-game-changer-in-javascript-architecture%23its-important-to-note-these-are-special-entry-points-they-are-only-a-few-kb-in-size-containing-a-special-webpack-runtime-that-can-interface-with-the-host-it-is-not-a-standard-entry-point--7%2F

          [18]

          https://zhuanlan.zhihu.com/p/115403616: https://link.juejin.cn/?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F115403616

          [19]

          https://developer.aliyun.com/article/755252: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.aliyun.com%2Farticle%2F755252

          [20]

          https://segmentfault.com/a/1190000022669224: https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000022669224

          [21]

          https://qiankun.umijs.org/zh/guide: https://link.juejin.cn/?target=https%3A%2F%2Fqiankun.umijs.org%2Fzh%2Fguide

          [22]

          https://github.com/umijs/qiankun: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fumijs%2Fqiankun

          [23]

          https://umijs.org/zh-CN/plugins/plugin-qiankun: https://link.juejin.cn/?target=https%3A%2F%2Fumijs.org%2Fzh-CN%2Fplugins%2Fplugin-qiankun

          [24]

          https://github.com/umijs/umi-plugin-qiankun/tree/master/examples: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fumijs%2Fumi-plugin-qiankun%2Ftree%2Fmaster%2Fexamples

          [25]

          https://zhuanlan.zhihu.com/p/131022025: https://link.juejin.cn/?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F131022025

          [26]

          https://www.yuque.com/kuitos/gky7yw/rhduwc: https://link.juejin.cn/?target=https%3A%2F%2Fwww.yuque.com%2Fkuitos%2Fgky7yw%2Frhduwc

          [27]

          https://juejin.cn/post/6846687602439897101: https://juejin.cn/post/6846687602439897101

          [28]

          https://mp.weixin.qq.com/s/xmcXz5GWSEYFy18APPHwlg: https://link.juejin.cn/?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FxmcXz5GWSEYFy18APPHwlg

          [29]

          https://mp.weixin.qq.com/s?__biz=Mzg4MjE5OTI4Mw==&mid=2247484715&idx=1&sn=7412b30838d652df5bdbc058e790bae2: https://link.juejin.cn/?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg4MjE5OTI4Mw%3D%3D%26mid%3D2247484715%26idx%3D1%26sn%3D7412b30838d652df5bdbc058e790bae2

          [30]

          https://stackoverflow.com/questions/37866237/click-event-not-firing-when-react-component-in-a-shadow-dom: https://link.juejin.cn/?target=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F37866237%2Fclick-event-not-firing-when-react-component-in-a-shadow-dom

          [31]

          https://github.com/bytedance/magic-microservices: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fbytedance%2Fmagic-microservices

          [32]

          https://github.com/puzzle-js/puzzle-js: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fpuzzle-js%2Fpuzzle-js

          [33]

          https://bit.dev/: https://link.juejin.cn/?target=https%3A%2F%2Fbit.dev%2F

          [34]

          https://github.com/teambit/bit: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fteambit%2Fbit

          [35]

          https://harmony-docs.bit.dev/getting-started/installing-bit/: https://link.juejin.cn/?target=https%3A%2F%2Fharmony-docs.bit.dev%2Fgetting-started%2Finstalling-bit%2F

          [36]

          https://alfajs.io/docs/intro.html: https://link.juejin.cn/?target=https%3A%2F%2Falfajs.io%2Fdocs%2Fintro.html

          [37]

          https://github.com/aliyun/alibabacloud-alfa: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Faliyun%2Falibabacloud-alfa

          [38]

          https://developer.aliyun.com/article/761449: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.aliyun.com%2Farticle%2F761449

          [39]

          https://mp.weixin.qq.com/s/IJMgMO1IeYw2Io8MN7WZWQ: https://link.juejin.cn/?target=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FIJMgMO1IeYw2Io8MN7WZWQ

          [40]

          https://segmentfault.com/a/1190000022684014: https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000022684014

          [41]

          https://segmentfault.com/a/1190000039795656: https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000039795656

          [42]

          https://github.com/aliyun/alibabacloud-alfa/blob/master/packages/core/browser-vm/src/Context.js: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Faliyun%2Falibabacloud-alfa%2Fblob%2Fmaster%2Fpackages%2Fcore%2Fbrowser-vm%2Fsrc%2FContext.js

          [43]

          https://www.figma.com/blog/how-we-built-the-figma-plugin-system/: https://link.juejin.cn/?target=https%3A%2F%2Fwww.figma.com%2Fblog%2Fhow-we-built-the-figma-plugin-system%2F

          [44]

          https://www.figma.com/plugin-docs/how-plugins-run/: https://link.juejin.cn/?target=https%3A%2F%2Fwww.figma.com%2Fplugin-docs%2Fhow-plugins-run%2F

          [45]

          https://github.com/tc39/proposal-realms/blob/main/explainer.md: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftc39%2Fproposal-realms%2Fblob%2Fmain%2Fexplainer.md

          [46]

          https://github.com/tc39/proposal-realms/blob/main/explainer.md: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Ftc39%2Fproposal-realms%2Fblob%2Fmain%2Fexplainer.md


          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久精视频| 黄色一级片 | 狠狠操在线视频 | 青青草伊人在线 | 中国女人真人一级毛片 |