<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>

          微前端如何做樣式隔離?

          共 5669字,需瀏覽 12分鐘

           ·

          2022-10-24 22:41

          本文為來自飛書 aPaaS Growth 研發(fā)團(tuán)隊(duì)成員的文章,已授權(quán) ELab 發(fā)布。

          aPaaS Growth 團(tuán)隊(duì) 專注在用戶可感知的、宏觀的 aPaaS 應(yīng)用的搭建流程,及租戶、應(yīng)用治理等產(chǎn)品路徑,致力于打造 aPaaS 平臺(tái)流暢的 “應(yīng)用交付” 流程和體驗(yàn),完善應(yīng)用構(gòu)建相關(guān)的生態(tài),加強(qiáng)應(yīng)用搭建的便捷性和可靠性,提升應(yīng)用的整體性能,從而助力 aPaaS 的用戶增長,與基礎(chǔ)團(tuán)隊(duì)一起推進(jìn) aPaaS 在企業(yè)內(nèi)外部的落地與提效。

          問題示例

          className 命名重復(fù)導(dǎo)致的樣式?jīng)_突

          我們先創(chuàng)建一個(gè)問題,驗(yàn)證樣式?jīng)_突的存在:

          在主應(yīng)用和子應(yīng)用上分別使用 div 元素插入一段標(biāo)題,兩個(gè) div 元素使用相同的 class 名 title,分別在 class 中設(shè)置文字顏色,主應(yīng)用 color 值為 yellow,子應(yīng)用為 red。

          由于子應(yīng)用的樣式晚于主應(yīng)用加載,所以主應(yīng)用的樣式會(huì)被覆蓋

          以上問題在同時(shí)加載多個(gè)子應(yīng)用時(shí)也會(huì)存在:各個(gè)應(yīng)用之間也可能存在同名的 className 或者給相同條件的選擇器添加了樣式, 那么最終只有優(yōu)先級(jí)最高的樣式才會(huì)生效。要確保應(yīng)用之間的樣式不會(huì)互相影響,就需要對(duì)應(yīng)用間的樣式進(jìn)行隔離。

          html、body 標(biāo)簽的樣式?jīng)_突

          html 、 body 標(biāo)簽, 在各個(gè)應(yīng)用中都是唯一的元素,其樣式必然會(huì)對(duì)主應(yīng)用的樣式產(chǎn)生影響。

          解決方案

          為了以上樣式?jīng)_突問題,通常有以下兩種思路:

          • 通過樣式命名 & 樣式優(yōu)先級(jí)解決
          • 通過宿主環(huán)境隔離來達(dá)到樣式隔離

          樣式命名 & 樣式優(yōu)先級(jí)

          假設(shè)各個(gè)應(yīng)用之間的樣式 className 都是全局唯一的, 那么不同 className 下的樣式就一定不會(huì)發(fā)生沖突。

          再加上樣式優(yōu)先級(jí)來配合解決,就能解決標(biāo)簽選擇器的樣式?jīng)_突:

          • 例如在原 className 、標(biāo)簽選擇器前面再添加一個(gè) selector
          • 標(biāo)簽選擇器 + 屬性選擇器

          子應(yīng)用改造

          這里需要處理的樣式也分為以下兩種:

          UI 組件庫等引入的全局樣式

          默認(rèn)情況下,UI 組件庫的 prefixCls 都是相同的,不過它們提供了 ConfigProvider 可以用來修改 UI 組件庫全局樣式的 prefixCls:全局化配置 ConfigProvider - Ant Design[1] 全局配置 ConfigProvider | ArcoDesign[2]

          自定義樣式

          通過 BEM、CSS Modules、 CSS in JS 等手段來獲得與其他應(yīng)用不同的選擇器名,來規(guī)避樣式?jīng)_突。

          或者直接使用 postcss 的插件,在編譯階段給所有樣式添加 prefix selector。

          https://github.com/RadValentin/postcss-prefix-selector

          主應(yīng)用在運(yùn)行時(shí)統(tǒng)一轉(zhuǎn)換樣式

          如果不想或者無法干涉子應(yīng)用的打包配置時(shí),我們也可以通過主應(yīng)用在運(yùn)行時(shí)給所有樣式規(guī)則添加 prefix selector,來提升樣式優(yōu)先級(jí)。

          比如 A 應(yīng)用的類選擇器 .title,在轉(zhuǎn)換后變成 #garfish_app_id_xxx .title#garfish_app_id_xxx 是子應(yīng)用最外層元素的 id,故保證該應(yīng)用下的樣式優(yōu)先級(jí)變高,并讓其只作用在當(dāng)前應(yīng)用下。

          當(dāng)然, 僅使用以上手段并不能解決子應(yīng)用 html、 body 標(biāo)簽給主應(yīng)用帶來的樣式影響,細(xì)心的你可能已經(jīng)發(fā)現(xiàn),garfish 會(huì)為每個(gè)子應(yīng)用創(chuàng)建一個(gè)假的 html 與 body 元素,然后對(duì)應(yīng)子元素的 html 、body 樣式都會(huì)應(yīng)用到這個(gè)假的 html、 body元素上。

          Garfish

          在 garfish 中是以插件來支持運(yùn)行時(shí)轉(zhuǎn)換樣式的:

          import { GarfishCssScope } from '@garfish/css-scope';
          Garfish.run({
            plugins: [
                GarfishCssScope({
                    fixBodyGetter: true,
                    excludes: ['appName'],
                }),
            ],
          })

          具體的源碼實(shí)現(xiàn)在這里:

          https://github.com/modern-js-dev/garfish/tree/1f83e8fb35fd2ac12785fc7410015c3cd23c3bd2/packages/css-scope

          優(yōu)點(diǎn)

          1. 支持大部分樣式隔離需求,能夠同時(shí)處理 UI 組件庫的全局樣式與自定義樣式,比較省心。

          缺點(diǎn)

          1. 運(yùn)行時(shí)處理樣式,會(huì)有一定性能損耗
          1. 如果其他子應(yīng)用或者主應(yīng)用中使用了 !important ...

          宿主環(huán)境隔離

          Shadow DOM

          附加并隱藏在常規(guī) DOM 下的節(jié)點(diǎn)叫做 Shadow DOM —— 它以 Shadow root 節(jié)點(diǎn)為起始根節(jié)點(diǎn),在這個(gè)根節(jié)點(diǎn)的下方,可以是任意元素,就和普通的 DOM 元素一樣,它可以通過方法添加子節(jié)點(diǎn)、設(shè)置屬性,以及為節(jié)點(diǎn)添加自己的樣式,隱藏的 DOM 樣式和其余 DOM 是完全隔離的,類似于 iframe 的樣式隔離效果。

          如何創(chuàng)建

          可以使用 shadowHostElement.attachShadow() 方法來將一個(gè) shadow root 附加到調(diào)用方法的元素上。它接受一個(gè)配置對(duì)象作為參數(shù),該對(duì)象有一個(gè) mode 屬性,值可以是 open 或者 closed

          let shadowRoot = shadowHostElement.attachShadow({mode: 'open'});
          let shadowRoot = shadowHostElement.attachShadow({mode: 'closed'});

          open 表示可以通過頁面內(nèi)的 JavaScript 來獲取 Shadow DOM,例如使用 Element.shadowRoot 屬性:

          let shadowRoot = shadowHostElement.shadowRoot;

          如果將 mode 設(shè)置為 closed,那么elementRef.shadowRoot 將會(huì)返回 null

          瀏覽器中的某些內(nèi)置元素就是如此,例如<video>,就包含了不可訪問的 Shadow DOM。

          為 shadow DOM 添加樣式

          我們可以通過創(chuàng)建<style> 元素為 Shadow DOM 添加樣式,也可以通過創(chuàng)建<link> 元素引用外部樣式表。

          // 使用 style 元素為 shadow DOM 添加樣式
          var style = document.createElement('style');
          style.textContent = `
              .title {
                  color: blue;
              }
          `;
          shadow.appendChild(style);

          // 使用 link 標(biāo)簽為 Shadow DOM 添加樣式
          const linkElem = document.createElement('link');
          linkElem.setAttribute('rel''stylesheet');
          linkElem.setAttribute('href''style.css');
          shadow.appendChild(linkElem);

          Shadow DOM 的事件模型

          當(dāng)一個(gè)事件從 Shadow DOM 中冒泡出來時(shí),事件的 target 屬性就會(huì)調(diào)整為 shadow DOM 的宿主。

          有些事件甚至不會(huì)冒泡到 Shadow DOM 之外。

          以下這些事件是會(huì)冒泡出去的:

          • Focus Events: blur, focus, focusin, focusout
          • Mouse Events: click, dblclick, mousedown, mouseenter, mousemove, etc.
          • Wheel Events: wheel
          • Input Events: beforeinput, input
          • Keyboard Events: keydown, keyup
          • Composition Events: compositionstart, compositionupdate, compositionend
          • DragEvent: dragstart, drag, dragend, drop, etc.

          如果 shadow dom 的模式為 open,調(diào)用event.composedPath()就會(huì)返回一個(gè)數(shù)組——包含事件冒泡經(jīng)過的所有元素。

          Garfish

          在 garfish 中使用也非常簡單,只需要一行配置即可開啟:

          https://github.com/modern-js-dev/garfish/blob/main/packages/utils/src/container.ts#L37

          Garfish.run({
            sandbox: {
              strictIsolation: true,
            },
          });

          優(yōu)點(diǎn)

          1. 完全隔離 CSS 樣式

          缺點(diǎn)

          1. 在使用一些 antd Select 組件的時(shí)候(很多情況下都是將 open 后的元素默認(rèn)添加到了 document.body 上 )這個(gè)時(shí)候它就跳過了陰影邊界,逃逸到主應(yīng)用里面,導(dǎo)致樣式丟失,這時(shí)候就需要去子應(yīng)用中手動(dòng)修正該彈出元素的掛載節(jié)點(diǎn)(例如使用 antd select 的 getPopupContainer)。

          1. 會(huì)與 react v17 之前的事件代理機(jī)制產(chǎn)生沖突[3]

          React v16 會(huì)各種事件處理函數(shù)代理到 document ,但是根據(jù) Shadow DOM 的事件模型,從 Shadow DOM 中冒泡出來的事件 target 都會(huì)被調(diào)整成 shadow host, 導(dǎo)致 react v16 無法通過 event.target 找到對(duì)應(yīng)的元素并觸發(fā)事件。

          garfish 源碼中的這部分就是在做 retarget:

          https://github.com/modern-js-dev/garfish/blob/main/packages/utils/src/container.ts#L42

          https://github.com/modern-js-dev/garfish/blob/1f83e8fb35fd2ac12785fc7410015c3cd23c3bd2/packages/utils/src/dispatchEvents.ts#L74

          React v17 不再將事件代理到 document 上,而是將事件代理到了 root Element 上,從而規(guī)避了這個(gè)問題( root element 也還在 shadow tree 中 )。

          關(guān)于 react v17 事件代理的更多內(nèi)容可以看看下面的文章:

          https://reactjs.org/blog/2020/08/10/react-v17-rc.html#changes-to-event-delegation

          1. 兼容性[4]還行,需要考慮
          1. Iconfont fontface
          1. ..

          總結(jié)

          樣式隔離實(shí)現(xiàn)起來不復(fù)雜,各種方案都有其局限性。目前比較穩(wěn)定的方案還是使用 css Modules 之類的工具配合團(tuán)隊(duì)之間協(xié)商好樣式前綴,從樣式命名 & 優(yōu)先級(jí)上解決問題。

          (主應(yīng)用的樣式依然可以影響到子應(yīng)用,優(yōu)先級(jí)也可能會(huì)被 !important 等操作被破壞,不過大多數(shù)場景下足夠了)

          但從長期來看,通過 Shadow DOM 完全隔離樣式還是很香的,也希望 Shadow DOM 與其他框架、組件庫結(jié)合使用的暗坑早日被填補(bǔ)完畢。

          ?? 謝謝支持

          以上便是本次分享的全部內(nèi)容,希望對(duì)你有所幫助^_^

          喜歡的話別忘了 分享、點(diǎn)贊、收藏 三連哦~。

          歡迎關(guān)注公眾號(hào) 前端 Sharing 收貨大廠一手好文章

          參考資料

          [1]

          全局化配置 ConfigProvider - Ant Design: https://ant.design/components/config-provider-cn/

          [2]

          全局配置 ConfigProvider | ArcoDesign: https://arco.design/react/components/config-provider

          [3]

          會(huì)與 react v17 之前的事件代理機(jī)制產(chǎn)生沖突: https://github.com/facebook/react/issues/9242

          [4]

          兼容性: https://caniuse.com/?search=shadow%20dom

          - END -


          瀏覽 67
          點(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>
                  人人看AV| 国产精品又黄又爽又色无遮挡 | 黄网站18禁 | 在线观看污污 | 五月天激情影院 |