溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Qiankun JS沙箱是怎么做隔離的

發(fā)布時(shí)間:2022-09-29 10:59:53 來源:億速云 閱讀:147 作者:iii 欄目:開發(fā)技術(shù)

這篇“Qiankun JS沙箱是怎么做隔離的”文章的知識點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Qiankun JS沙箱是怎么做隔離的”文章吧。

    前言

    當(dāng)我寫 window.a = 1 的時(shí)候,a 是怎么被掛載到這些 XXXSandbox 上的呢?又或者我直接云修改 window.a = 123 時(shí),JS 沙箱到底是怎么隔離這個(gè) a 的呢?

    總不能這樣吧:

    window = window.sandbox
    window.a = 1 // window.sandbox.a = 1

    復(fù)習(xí)一下沙箱

    SanpshotSandbox

    第一種是快照沙箱。

    它的原理是:把主應(yīng)用的 window 對象做淺拷貝,將 window 的鍵值對存成一個(gè) Hash Map。之后無論微應(yīng)用對 window 做任何改動,當(dāng)要在恢復(fù)環(huán)境時(shí),把這個(gè) Hash Map 又應(yīng)用到 window 上就可以了。 大概如下圖所示。

    Qiankun JS沙箱是怎么做隔離的

    稍微做下小結(jié):

    • 微應(yīng)用 mount 時(shí)

      • 先把上一次記錄的變更 modifyPropsMap 應(yīng)用到微應(yīng)用的全局 window,沒有則跳過

      • 淺復(fù)制主應(yīng)用的 window key-value 快照,用于下次恢復(fù)全局環(huán)境

    • 微應(yīng)用 unmount 時(shí)

      • 將當(dāng)前微應(yīng)用 window 的 key-value 和 快照 的 key-value 進(jìn)行 Diff,Diff 出來的結(jié)果用于下次恢復(fù)微應(yīng)用環(huán)境的依據(jù)

      • 將上次快照的 key-value 拷貝到主應(yīng)用的 window 上,以此恢復(fù)環(huán)境

    LegacySandbox

    上面的 SnapshotSandbox 有一個(gè)問題:每次微應(yīng)用 unmount 時(shí)都要對每個(gè)屬性值做一次 Diff,類似這樣:

    for (const prop in window) {
      if (window[prop] !== this.windowSnapshot[prop]) {
        // 記錄微應(yīng)用的變更
        this.modifyPropsMap[prop] = window[prop];
        // 恢復(fù)主應(yīng)用的環(huán)境
        window[prop] = this.windowSnapshot[prop];
      }
    }

    如果有 1000 個(gè)屬性就要對比 1000 次,不是那么優(yōu)雅。

    LegacySandbox 的想法則是 通過監(jiān)聽對 window 的修改來直接記錄 Diff 內(nèi)容,因?yàn)橹灰獙?window 屬性進(jìn)行設(shè)置,那么就會有兩種情況:

    • 如果是新增屬性,那么存到 addedMap 里

    • 如果是更新屬性,那么把原來的鍵值存到 prevMap,把新的鍵值存到 newMap

    (當(dāng)然這里的變量名做了簡化)

    通過 addedMap, prevMap 和 newMap 這三個(gè)變量就能反推出微應(yīng)用以及原來環(huán)境的變化,qiankun 也能以此作為恢復(fù)環(huán)境的依據(jù)。

    Qiankun JS沙箱是怎么做隔離的

    ProxySandbox

    前面兩種沙箱都是 單例模式 下使用的沙箱。也即一個(gè)頁面中只能同時(shí)展示一個(gè)微應(yīng)用,而且無論是 set 還是 get 依然是直接操作 window 對象。

    在這樣單例模式下,當(dāng)微應(yīng)用修改全局變量時(shí)依然會在原來的 window 上做修改,因此如果在同一個(gè)路由頁面下展示多個(gè)微應(yīng)用時(shí),依然會有環(huán)境變量污染的問題。

    為了避免真實(shí)的 window 被污染,qiankun 實(shí)現(xiàn)了 ProxySandbox。它的想法是:

    • 把當(dāng)前 window 的一些原生屬性(如document, location等)拷貝出來,單獨(dú)放在一個(gè)對象上,這個(gè)對象也稱為 fakeWindow

    • 之后對每個(gè)微應(yīng)用分配一個(gè) fakeWindow

    • 當(dāng)微應(yīng)用修改全局變量時(shí):

      • 如果是原生屬性,則修改全局的 window

      • 如果是原生屬性,則修改 fakeWindow 里的內(nèi)容

    • 微應(yīng)用獲取全局變量時(shí):

      • 如果是原生屬性,則從 window 里拿

      • 如果不是原生屬性,則優(yōu)先從 fakeWindow 里獲取

    這樣一來連恢復(fù)環(huán)境都不需要了,因?yàn)槊總€(gè)微應(yīng)用都有自己一個(gè)環(huán)境,當(dāng)在 active 時(shí)就給這個(gè)微應(yīng)用分配一個(gè) fakeWindow,當(dāng) inactive 時(shí)就把這個(gè) fakeWindow 存起來,以便之后再利用。

    Qiankun JS沙箱是怎么做隔離的

    隔離原理

    看完上面,你大概也知道了這些沙箱是怎么恢復(fù)環(huán)境的 但是,回到我們的問題:qiankun 是怎么把 a 和這些沙箱聯(lián)系起來呢?也即寫下 window.a = 1 是怎么做到對 a 變量隔離的呢?

    這個(gè)邏輯的實(shí)現(xiàn)并不在 qiankun 的源碼里,而是在它所依賴的 import-html-entry 中,這里做一下簡化:

    const executableScript = `
      ;(function(window, self, globalThis){
        ;${scriptText}${sourceUrl}
      }).bind(window.proxy)(window.proxy, window.proxy, window.proxy);
    `
    eval.call(window, executableScript)

    把上面字符串代碼展開來看看:

    function fn(window, self, globalThis) {
      // 你的 JavaScript code
    }
    const bindedFn = fn.bind(window.proxy);
    bindedFn(window.proxy, window.proxy, window.proxy);

    可以發(fā)現(xiàn)這里的代碼做了三件事:

    • 把要執(zhí)行 JS 代碼放在一個(gè)立即執(zhí)行函數(shù)中,且函數(shù)入?yún)⒂?window, self, globalThis

    • 給這個(gè)函數(shù) 綁定上下文 window.proxy

    • 執(zhí)行這個(gè)函數(shù),并 把上面提到的沙箱對象 window.proxy 作為入?yún)⒎謩e傳入

    因此,當(dāng)我們在 JS 文件里有 window.a = 1 時(shí),實(shí)際上會變成:

    function fn(window, self, globalThis) {
      window.a = 1;
    }
    const bindedFn = fn.bind(window.proxy);
    bindedFn(window.proxy, window.proxy, window.proxy);

    那么此時(shí),window.a 的 window 就不是全局 window 而是 fn 的入?yún)?window 了。又因?yàn)槲覀儼?window.proxy 作為入?yún)魅?,所?window.a 實(shí)際上為 window.proxy.a = 1。這也正好解釋了 qiankun 的 JS 隔離邏輯。

    XXX is undefined

    不知道看完上面的實(shí)現(xiàn),你有沒有發(fā)現(xiàn)問題。

    假如現(xiàn)在代碼里有隱式聲明或調(diào)用全局對象的代碼:

    add = (a, b) => {
      return a + b
    }
    add(1, 2)

    當(dāng)這樣調(diào)用 add 時(shí),上下文 this 則為剛剛綁定的 window.proxy。由于隱式聲明 add 不會自動掛載到 window.proxy 上,所以當(dāng)執(zhí)行 add,eval 就會報(bào) add is undefined。詳見 這個(gè) Issue。

    不要覺得這種情況不會發(fā)生,實(shí)際上,這還是挺常見的:

    • 老舊的第三方 SDK JS 文件

    • Webpack 插件引入的 JS

    • 公司網(wǎng)關(guān)層自動注入的 JS

    • 等等...

    我之前就遇到過這種情況:比如下面 Webpack 會注入腳手架定義好的 CDN 資源重試邏輯:

    <script>
      var __JS_RETRY__ = {};
      function __rpReport(data) {
        console.log('__rpReport');
      }
      function __rpJsReport(loadType, msidType, url) {
        console.log('__rpJsReport');
      }
      function __retryPlugin(event) {
        console.log('retryPlugin')
      }
      // 改成下面就可以了
      // window.__JS_RETRY__ = {};
      //
      // window.__rpReport = (data) => {
      //     console.log('__rpReport');
      // }
      //
      // window.__rpJsReport = (loadType, msidType, url) => {
      //     console.log('__rpJsReport');
      // }
      //
      // window.__retryPlugin = (event) => {
      //     console.log('retryPlugin')
      // }
    </script>

    這個(gè)問題的解決的方法也很簡單:

    • 把代碼 a = 1 改成 window.a

    • 添加全局聲明 window a

    這樣一來,你就得每次打包代碼以及發(fā)布時(shí)執(zhí)行一個(gè)腳本來做這些文本替換,非常麻煩。而京東的新微應(yīng)用框架 MicroApp 則提供了一套插件系統(tǒng):

    Qiankun JS沙箱是怎么做隔離的

    它可以讓開發(fā)者在執(zhí)行 JS 前去做代碼文本的替換:

    import microApp from '@micro-zoe/micro-app'
    microApp.start({
      plugins: {
        // ...
        modules: {
          'appName1': [{
            loader(code, url, options) {
              if (url === 'xxx.js') {
                // 替換有問題的代碼
                code = code.replace('var abc =', 'window.abc =')
              }
              return code
            }
          }],
        }
      }
    })

    如果要對接別的團(tuán)隊(duì)的微應(yīng)用時(shí),而且正好他們有 a = 1 這樣的代碼,那么在加載微應(yīng)用的時(shí)候直接修復(fù)全局變量的問題,不需要通知他們修改,也不失為一種策略吧。

    以上就是關(guān)于“Qiankun JS沙箱是怎么做隔離的”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI