日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

基于 iframe 的全新微前端方案

發(fā)布時(shí)間:2024/2/28 HTML 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于 iframe 的全新微前端方案 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

作者:damyxu,騰訊 PCG 前端開發(fā)工程師

iframe是一個(gè)天然的微前端方案,但受限于跨域的嚴(yán)格限制而無法很好的應(yīng)用,本文介紹一種基于 iframe 的全新微前端方案,繼承iframe的優(yōu)點(diǎn),補(bǔ)足 iframe 的缺點(diǎn),讓 iframe 煥發(fā)新生。

背景

前端開發(fā)中我們對iframe已經(jīng)非常熟悉了,那么iframe的作用是什么?可以歸納如下:

在一個(gè)web應(yīng)用中可以獨(dú)立的運(yùn)行另一個(gè)web應(yīng)用

這個(gè)概念已經(jīng)和微前端不謀而合,相對于目前配置復(fù)雜、高適配成本的微前端方案來說,采用iframe方案具有一些顯著的優(yōu)點(diǎn)

  • 非常簡單,使用沒有任何心智負(fù)擔(dān)

  • 隔離完美,無論是 js、css、dom 都完全隔離開來

  • 多應(yīng)用激活,頁面上可以擺放多個(gè)iframe來組合業(yè)務(wù)

但是開發(fā)者又厭惡使用iframe,因?yàn)?strong>缺點(diǎn)也非常明顯:

  • 路由狀態(tài)丟失,刷新一下,iframe 的 url 狀態(tài)就丟失了

  • dom 割裂嚴(yán)重,彈窗只能在 iframe 內(nèi)部展示,無法覆蓋全局

  • 通信非常困難,只能通過 postmessage 傳遞序列化的消息

  • 白屏?xí)r間太長,對于SPA 應(yīng)用應(yīng)用來說無法接受

能否打造一個(gè)完美的iframe,保留所有的優(yōu)點(diǎn)的同時(shí),解決掉所有的缺點(diǎn)呢?

無界方案

無界微前端框架通過繼承iframe的優(yōu)點(diǎn),解決iframe的缺點(diǎn),打造一個(gè)接近完美的iframe方案。

來看無界如何一步一步的解決iframe的問題,假設(shè)我們有 A 應(yīng)用,想要加載 B 應(yīng)用:

在應(yīng)用 A 中構(gòu)造一個(gè)shadow和iframe,然后將應(yīng)用 B 的html寫入shadow中,js運(yùn)行在iframe中,注意iframe的url,iframe保持和主應(yīng)用同域但是保留子應(yīng)用的路徑信息,這樣子應(yīng)用的js可以運(yùn)行在iframe的location和history中保持路由正確。

image-20211206160113792

在iframe中攔截document對象,統(tǒng)一將dom指向shadowRoot,此時(shí)比如新建元素、彈窗或者冒泡組件就可以正常約束在shadowRoot內(nèi)部。

接下來的三步分別解決iframe的三個(gè)缺點(diǎn):

  • ? dom 割裂嚴(yán)重的問題,主應(yīng)用提供一個(gè)容器給到shadowRoot插拔,shadowRoot內(nèi)部的彈窗也就可以覆蓋到整個(gè)應(yīng)用 A

  • ? 路由狀態(tài)丟失的問題,瀏覽器的前進(jìn)后退可以天然的作用到iframe上,此時(shí)監(jiān)聽iframe的路由變化并同步到主應(yīng)用,如果刷新瀏覽器,就可以從 url 讀回保存的路由

  • ? 通信非常困難的問題,iframe和主應(yīng)用是同域的,天然的共享內(nèi)存通信,而且無界提供了一個(gè)去中心化的事件機(jī)制

將這套機(jī)制封裝進(jìn)wujie框架:

我們可以發(fā)現(xiàn):

  • ? 首次白屏的問題,wujie實(shí)例可以提前實(shí)例化,包括shadowRoot、iframe的創(chuàng)建、js的執(zhí)行,這樣極大的加快子應(yīng)用第一次打開的時(shí)間

  • ? 切換白屏的問題,一旦wujie實(shí)例可以緩存下來,子應(yīng)用的切換成本變的極低,如果采用保活模式,那么相當(dāng)于shadowRoot的插拔

image-20211206160227875

由于子應(yīng)用完全獨(dú)立的運(yùn)行在iframe內(nèi),路由依賴iframe的location和history,我們還可以在一張頁面上同時(shí)激活多個(gè)子應(yīng)用,由于iframe和主應(yīng)用處于同一個(gè)top-level browsing context,因此瀏覽器前進(jìn)、后退都可以作用到到子應(yīng)用:

image-20211206160244704

通過以上方法,無界方案可以做到:

  • ? 非常簡單,使用沒有任何心智負(fù)擔(dān)

  • ? 隔離完美,無論是 js、css、dom 都完全隔離開來

  • ? 多應(yīng)用激活,頁面上可以擺放多個(gè)iframe來組合業(yè)務(wù)

  • 路由狀態(tài)丟失,刷新一下,iframe 的 url 狀態(tài)就丟失了

  • dom 割裂嚴(yán)重,彈窗只能在 iframe 內(nèi)部展示,無法覆蓋全局

  • 通信非常困難,只能通過 postmessage 傳遞序列化的消息

  • 白屏?xí)r間太長,對于SPA 應(yīng)用應(yīng)用來說無法接受

使用無界

如果主應(yīng)用是vue框架:

安裝

`npm?i?@tencent/wujie-vue?-S`

引入

mport?WujieVue?from?"@tencent/wujie-vue"; Vue.use(WujieVue);

使用

<WujieVuewidth="100%"height="100%"name="xxx"url="xxx":sync="true":fetch="fetch":props="props"@xxx="handleXXX" ></WujieVue>

其他框架也會(huì)在近期上線

適配成本

無界的適配成本非常低

對于主應(yīng)用無需做任何改造

對于子應(yīng)用:

  • 前提,必須開放跨域配置,因?yàn)樽討?yīng)用是在主應(yīng)用域內(nèi)請求和運(yùn)行的

  • 對webpack應(yīng)用,修改動(dòng)態(tài)加載路徑

  • 如果子應(yīng)用保活模式則無需進(jìn)一步修改,非保活則需要將實(shí)例化掛載到無界生命周期內(nèi)

if?(window.__POWERED_BY_WUJIE__)?{let?instance;window.__WUJIE_MOUNT?=?()?=>?{instance?=?new?Vue({?router,?render:?(h)?=>?h(App)?}).$mount("#app");};window.__WUJIE_UNMOUNT?=?()?=>?{instance.$destroy();}; }?else?{new?Vue({?router,?render:?(h)?=>?h(App)?}).$mount("#app"); }

實(shí)現(xiàn)細(xì)節(jié)

實(shí)現(xiàn)一個(gè)純凈的 iframe

子應(yīng)用運(yùn)行在一個(gè)和主應(yīng)用同域的iframe中,設(shè)置src為替換了主域名host的子應(yīng)用url,子應(yīng)用路由只取location的pathname和hash

但是一旦設(shè)置src后,iframe由于同域,會(huì)加載主應(yīng)用的html、js,所以必須在iframe實(shí)例化完成并且還沒有加載完html時(shí)中斷加載,防止污染子應(yīng)用

此時(shí)可以采用輪詢監(jiān)聽document.readyState狀態(tài)來及時(shí)中斷,對于一些瀏覽器比如safari狀態(tài)不準(zhǔn)確,可以在wujie主動(dòng)拋錯(cuò)來防止有主應(yīng)用的js運(yùn)行

iframe 數(shù)據(jù)劫持和注入

子應(yīng)用的代碼 code 在 iframe 內(nèi)部訪問 window,document、location 都被劫持到相應(yīng)的 proxy,并且還會(huì)注入$wujie對象供子應(yīng)用調(diào)用

const?script?=?`(function(window,?self,?global,?document,?location,?$wujie)?{${code}\n}).bind(window.__WUJIE.proxy)(window.__WUJIE.proxy,window.__WUJIE.proxy,window.__WUJIE.proxy,window.__WUJIE.proxy.document,window.__WUJIE.proxy.location,window.__WUJIE.provide);`;

iframe 和 shadowRoot 副作用的處理

iframe 內(nèi)部的副作用處理在初始化iframe時(shí)進(jìn)行,主要分為如下幾部

/***?1、location劫持后的數(shù)據(jù)修改回來,防止跨域錯(cuò)誤*?2、同步路由到主應(yīng)用*/ patchIframeHistory(iframeWindow,?appPublicPath,?mainPublicPath); /***?對window.addEventListener進(jìn)行劫持,比如resize事件必須是監(jiān)聽主應(yīng)用的*/ patchIframeEvents(iframeWindow); /***?注入私有變量*/ patchIframeVariable(iframeWindow,?appPublicPath); /***?將有DOM副作用的統(tǒng)一在此修改,比如mutationObserver必須調(diào)用主應(yīng)用的*/ patchIframeDomEffect(iframeWindow); /***?子應(yīng)用前進(jìn)后退,同步路由到主應(yīng)用*/ syncIframeUrlToWindow(iframeWindow);

ShadowRoot 內(nèi)部的副作用必須進(jìn)行處理,主要處理的就是shadowRoot的head和body

shadowRoot.head.appendChild?=?getOverwrittenAppendChildOrInsertBefore({rawDOMAppendOrInsertBefore:?rawHeadAppendChild})?as?typeof?rawHeadAppendChildshadowRoot.head.insertBefore?=?getOverwrittenAppendChildOrInsertBefore({rawDOMAppendOrInsertBefore:?rawHeadInsertBefore?as?any})?as?typeof?rawHeadInsertBeforeshadowRoot.body.appendChild?=?getOverwrittenAppendChildOrInsertBefore({rawDOMAppendOrInsertBefore:?rawBodyAppendChild})?as?typeof?rawBodyAppendChildshadowRoot.body.insertBefore?=?getOverwrittenAppendChildOrInsertBefore({rawDOMAppendOrInsertBefore:?rawBodyInsertBefore?as?any})?as?typeof?rawBodyInsertBefore

getOverwrittenAppendChildOrInsertBefore主要是處理四種類型標(biāo)簽:

  • link/style標(biāo)簽

    收集stylesheetElement并用于子應(yīng)用重新激活重建樣式

  • script標(biāo)簽

    動(dòng)態(tài)插入的script標(biāo)簽必須從ShadowRoot轉(zhuǎn)移至iframe內(nèi)部執(zhí)行

  • iframe標(biāo)簽

    修復(fù)iframe的指向?qū)?yīng)子應(yīng)用iframe的window

iframe 的 document 改造

由于js在iframe運(yùn)行需要和shadowRoot,包括元素創(chuàng)建、事件綁定等等,將iframe的document進(jìn)行劫持:

  • 所有的元素的查詢?nèi)看淼絪hadowRoot內(nèi)去查詢

  • head和body代理到shadowRoot的對應(yīng)html元素上

iframe 的 location 改造

將iframe的location進(jìn)行劫持:

  • 由于iframe的url的host是主應(yīng)用的,所以需要將host改回子應(yīng)用自己的

  • 對于location.href特殊邏輯的處理

總結(jié)

通過上面原理以及細(xì)節(jié)的闡述,我們可以得出無界微前端框架的幾點(diǎn)優(yōu)勢:

  • 多應(yīng)用同時(shí)激活在線框架具備同時(shí)激活多應(yīng)用,并保持這些應(yīng)用路由同步的能力

  • 組件式的使用方式無需注冊,更無需路由適配,在組件內(nèi)使用,跟隨組件裝載、卸載

  • 應(yīng)用級別的 keep-alive子應(yīng)用開啟保活模式后,應(yīng)用發(fā)生切換時(shí)整個(gè)子應(yīng)用的狀態(tài)可以保存下來不丟失,結(jié)合預(yù)執(zhí)行模式可以獲得類似ssr的打開體驗(yàn)

  • 純凈無污染

    • 無界利用iframe和ShadowRoot來搭建天然的js隔離沙箱和css隔離沙箱

    • 利用iframe的history和主應(yīng)用的history在同一個(gè)top-level browsing context來搭建天然的路由同步機(jī)制

    • 副作用局限在沙箱內(nèi)部,子應(yīng)用切換無需任何清理工作,沒有額外的切換成本

  • 性能和體積兼具

    • 子應(yīng)用執(zhí)行性能和原生一致,子應(yīng)用實(shí)例instance運(yùn)行在iframe的window上下文中,避免with(proxyWindow){code}這樣指定代碼執(zhí)行上下文導(dǎo)致的性能下降,但是多了實(shí)例化iframe的一次性的開銷,可以通過proloadApp提前實(shí)例化

    • 包體積只有11kb,非常輕量,借助iframe和ShadowRoot來實(shí)現(xiàn)沙箱,極大的減小了代碼量

  • 開箱即用不管是樣式的兼容、路由的處理、彈窗的處理、熱更新的加載,子應(yīng)用完成接入即可開箱即用無需額外處理,應(yīng)用接入成本也極低

相應(yīng)的也有所不足:

  • 內(nèi)存占用較高,為了降低子應(yīng)用的白屏?xí)r間,將未激活子應(yīng)用的shadowRoot和iframe常駐內(nèi)存并且保活模式下每張頁面都需要獨(dú)占一個(gè)wujie實(shí)例,內(nèi)存開銷較大

  • 兼容性一般,目前用到了瀏覽器的shadowRoot和proxy能力,并且沒有做降級方案

  • iframe劫持document到shadowRoot時(shí),某些第三方庫可能無法兼容導(dǎo)致穿透

近期好文:

微信支付團(tuán)隊(duì)精益研發(fā)實(shí)踐總結(jié)

梳理正則表達(dá)式發(fā)展史

程序員媽媽的“work-life balance”,直面想象中的困難

程序員教你做咖啡

超強(qiáng)干貨來襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生

總結(jié)

以上是生活随笔為你收集整理的基于 iframe 的全新微前端方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。