1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        細(xì)述字節(jié)的微前端體系

        共 16852字,需瀏覽 34分鐘

         ·

        2021-09-15 23:12

        點(diǎn)擊上方關(guān)注 前端技術(shù)江湖,一起學(xué)習(xí),天天進(jìn)步


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

        總的來說「微前端」這個(gè)概念從造出來到發(fā)展如今,還處于一個(gè)百花齊放(各做各的)的發(fā)展中,沒有形成統(tǒng)一的(市場占有高的)共識(shí) / 標(biāo)準(zhǔn);各個(gè)大廠 / 社區(qū)對這個(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ú)立上線」「互不干擾」

        對我們來說重要的是 「獨(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 簡化成最小情況,就一個(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ì)去異步加載對應(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 為例,簡化流程為:

        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)渲染對應(yīng)路由內(nèi)容

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

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

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

        微前端體系

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

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

        治理體系

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

        在目前調(diào)研結(jié)果來看,微前端的落地使用一定需要配套這么個(gè)管理平臺(tái),雖然說的這么絕對,它也就比非微前端時(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ā)配套

        文檔這部分特別重要,也不只是對微前端框架的介紹文檔,包括開發(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ì)上是對微前端形態(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)程加載的門檻,于是物料市場這種東西也順勢就會(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è)場景下,簡單區(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ù)注冊的子應(yīng)用,通過給定的 url,加載約定格式的子應(yīng)用入口,并掛載到給定位置
          • 部分框架是根據(jù)類似 manifest 的數(shù)據(jù),來獲取子應(yīng)用注冊情況以及入口地址
          • 部分框架支持和管理平臺(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è)界目前常用兩種入口格式, HTML 和 JS
          • 父子入口組合(即確定依賴關(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)用
          • 也就是對子應(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í),先對全局 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í)對 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 作用域的對象,通過白名單屬性限制能訪問真正 window 上的部分元素,通過 Proxy 讓刪除 / 添加全局變量 / api 時(shí)不會(huì)對真正全局 window 產(chǎn)生影響;

        同時(shí)對 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 逃逸問題,可以取一個(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 對象傳給子應(yīng)用做 window;

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

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

        // 簡化偽代碼示例
        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)問題;

        大部分都會(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 對應(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 事件不支持外,還有一個(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)用對應(yīng)再放一個(gè) Shadow DOM 的 div,這個(gè) div 和 document.body 的定位、大小、margin/padding 屬性都完全一樣,等同于覆蓋在 body 之上,并且內(nèi)部完全同步了對應(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 對齊,基本解決問題;

        <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 的做法可能會(huì)有無法同步、遺漏,等問題;

        • 比如對 <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 樣式影響,但對應(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ì)劃要改造,但在此之前做了微前端改造,那么之后路由改造的兼容可能更難做;

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

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

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

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

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

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

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

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

        公共依賴復(fù)用

        微前端不解決復(fù)用問題,「依賴復(fù)用」本身不是微前端框架該做的事 ,有些依賴是不能也不應(yīng)該復(fù)用的(如代碼的執(zhí)行會(huì)對依賴本身的內(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ā)包
        • 對應(yīng)模塊完整移動(dòng)到其他倉庫(或 monorepo 目錄,如 apps/ ),并能獨(dú)立啟動(dòng)開發(fā) (因?yàn)槟荛_發(fā)就能做部署了)
        • 剩下就是按照對應(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]

        綜述

        【推薦】是對微前端的一些總體概覽,包括設(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)用場景調(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)境。


        參考資料

        [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

        The End

        歡迎自薦投稿到《前端技術(shù)江湖》,如果你覺得這篇內(nèi)容對你挺有啟發(fā),記得點(diǎn)個(gè) 「在看」


        點(diǎn)個(gè)『在看』支持下 

        瀏覽 96
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            大香蕉伊人久久 | 青娱乐自拍视频 | 妇女撒尿bbbb | 漂亮一区二区三区 | 久久久久久三级 | 美女被爆羞羞视频网站视频动漫 | 影音先锋久久精品视频 | 黄色视频网站免费在线观看 | 国产传媒操逼视频 | 操www|