自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何“取巧”實(shí)現(xiàn)一個(gè)微前端沙箱?

開發(fā) 架構(gòu)
如今微前端已經(jīng)成為前端領(lǐng)域比較火爆的話題,在技術(shù)方面,微前端有一個(gè)始終繞不過去的話題就是前端沙箱。本文將分享阿里云開放平臺(tái)微前端方案的沙箱實(shí)現(xiàn)原理,具體探討在微前端領(lǐng)域如何實(shí)現(xiàn)前端沙箱。

 如今微前端已經(jīng)成為前端領(lǐng)域比較火爆的話題,在技術(shù)方面,微前端有一個(gè)始終繞不過去的話題就是前端沙箱。本文將分享阿里云開放平臺(tái)微前端方案的沙箱實(shí)現(xiàn)原理,具體探討在微前端領(lǐng)域如何實(shí)現(xiàn)前端沙箱。

[[326638]]

背景

應(yīng)用沙箱可能是微前端技術(shù)體系里面最有意思的部分。一般來說沙箱是微前端技術(shù)體系中不是必須要做的事情,因?yàn)槿绻?guī)范做的足夠好,是能夠避免掉一些變量沖突讀寫,CSS 樣式?jīng)_突的情況。但是如果你在一個(gè)足夠大的體系中,總不能僅僅通過規(guī)范來保證應(yīng)用的可靠性,還是需要技術(shù)手段去治理運(yùn)行時(shí)的一些沖突問題,這個(gè)也是沙箱方案成為微前端技術(shù)體系的一部分原因。

首先縱觀各類技術(shù)方案,有一個(gè)大前提決定了這個(gè)沙箱如何做:最終微應(yīng)用是單實(shí)例 or 多實(shí)例存在宿主應(yīng)用中。這個(gè)直接決定了這個(gè)沙箱的復(fù)雜度和技術(shù)方案。

  • 單實(shí)例:同一個(gè)時(shí)刻只有一個(gè)微應(yīng)用實(shí)例存在,此刻瀏覽器所有瀏覽器資源都是這個(gè)應(yīng)用獨(dú)占的,方案要解決的很大程度是應(yīng)用切換的時(shí)候的清理和現(xiàn)場(chǎng)恢復(fù)。比較輕量,實(shí)現(xiàn)起來也簡(jiǎn)單。
  • 多實(shí)例:資源不是應(yīng)用獨(dú)占,就要解決資源共享的情況,比如路由,樣式,全局變量讀寫,DOM??赡苄枰紤]的情況比較多,實(shí)現(xiàn)較為復(fù)雜。

最開始我們的想法是:

從業(yè)務(wù)場(chǎng)景:我們可能存在的情況是當(dāng)用戶操作一個(gè)產(chǎn)品 A 的同時(shí)和另一個(gè)產(chǎn)品 B 發(fā)生了關(guān)聯(lián)操作,需要喚醒應(yīng)用 B 做操作。雖然從產(chǎn)品維度可以規(guī)避掉,比如先切到 B,然后切回 A,但是從某種程度上因?yàn)榧夹g(shù)的原因,我們限制了產(chǎn)品交互的發(fā)揮。

從技術(shù)角度:解決了多實(shí)例當(dāng)然單實(shí)例的場(chǎng)景也不在話下,并且單實(shí)例的方案某種程度上給編碼上帶來了一定復(fù)雜度,比如業(yè)務(wù)代碼需要自己做業(yè)務(wù)上下文的切換。

最近 qiankun 2 也轉(zhuǎn)變了思路,從單實(shí)例的支持到開始支持多實(shí)例,多多少少也側(cè)面說明了,多實(shí)例是一個(gè)值得投入和技術(shù)攻克的場(chǎng)景。

基于上面的考量,我們就開始了我們 Browser VM 沙箱的實(shí)現(xiàn)探索??偨Y(jié)起來可以用下圖表示:

 

JavaScript 沙箱實(shí)現(xiàn)

沙箱環(huán)境構(gòu)造

要實(shí)現(xiàn)沙箱,我們需要隔離掉瀏覽器的原生對(duì)象,但是如何隔離,建立一個(gè)沙箱環(huán)境呢?Node 中 有 vm 模塊,來實(shí)現(xiàn)類似的能力,但是瀏覽器就不行了,但是我們可以利用閉包的能力、利用變量作用域去模擬一個(gè)沙箱環(huán)境,比如下面的代碼:

  1. function foo(window) { 
  2.   console.log(window.document); 
  3. foo({ 
  4.     document: {}; 
  5. }); 

比如這段代碼的輸出一定是 {},而不是原生瀏覽器的 document。

所以 ConsoleOS(面向阿里云管體系的微前端方案) 實(shí)現(xiàn)了一個(gè) Wepback 的插件,在應(yīng)用代碼構(gòu)建的時(shí)候給子應(yīng)用代碼加上一層 wrap 代碼,創(chuàng)建一個(gè)閉包,把需要隔離的瀏覽器原生對(duì)象變成從下面函數(shù)閉包中獲取,從而我們可以在應(yīng)用加載的時(shí)候,傳入模擬的 window、document 之類的對(duì)象。

  1. // 打包代碼 
  2. __CONSOLE_OS_GLOBAL_HOOK__(id, function (require, module, exports, {window, document, location, history}) { 
  3.   /* 打包代碼 */ 
  4. }) 
  5. function __CONSOLE_OS_GLOBAL_HOOK__(id, entry) { 
  6.   entry(require, module, exports, {window, document, location, history}) 

當(dāng)然也可以不靠工程化的手段來實(shí)現(xiàn),也可以通過請(qǐng)求腳本,然后在運(yùn)行時(shí)拼接這段代碼,然后eval 或者 new Function 來達(dá)到相同的目的。

原生對(duì)象模擬

沙箱隔離能力有了,剩下的問題就是如何實(shí)現(xiàn)這一堆瀏覽器的原生對(duì)象了。最開始的想法是我們根據(jù) ECMA 的規(guī)范實(shí)現(xiàn)(現(xiàn)在仍然有類似的想法),但是發(fā)現(xiàn)成本太高。不過在我們各種實(shí)驗(yàn)之后,發(fā)現(xiàn)了一個(gè)很“取巧”的做法,我們可以 new iframe 對(duì)象,把里面的原生瀏覽器對(duì)象通過contentWindow 取出來,因?yàn)檫@些對(duì)象天然隔離,就省去了自己實(shí)現(xiàn)的成本。

  1. const iframe = document.createElement( 'iframe' ); 

當(dāng)然里面有很多的細(xì)節(jié)需要考量,比如:只有同域的 iframe 才能取出對(duì)應(yīng)的的contentWindow。所以需要提供一個(gè)宿主應(yīng)用空的同域 URL 來作為這個(gè) iframe 初始加載的 URL。當(dāng)然根據(jù) HTML 的規(guī)范,這個(gè) URL 用了 about:blank 一定保證同域,也不會(huì)發(fā)生資源加載,但是會(huì)發(fā)生和這個(gè) iframe 中關(guān)聯(lián)的 history 不能被操作,這個(gè)時(shí)候路由的變換只能變成 hash 模式。

如下圖所示,我們?nèi)〕鰧?duì)應(yīng)的 iframe 中原生的對(duì)象之后,就會(huì)對(duì)特定需要隔離的對(duì)象生成對(duì)應(yīng)的 Proxy, 然后對(duì)一些屬性獲取和屬性設(shè)置,做一些特定的設(shè)置,比如 window.document 需要返回特定的沙箱 document 而不是當(dāng)前瀏覽器的 document。

  1. class Window { 
  2.     constructor(options, context, frame) { 
  3.     return new Proxy(frame.contentWindow, { 
  4.         set(target, name, value) { 
  5.         target[name] = value; 
  6.         return true
  7.       }, 
  8.        
  9.       get(target, name) { 
  10.         switch( name ) { 
  11.           case 'document'
  12.             return context.document; 
  13.           default
  14.         } 
  15.          
  16.         if( typeof target[ name ] === 'function' && /^[a-z]/.test( name ) ){ 
  17.           return target[ name ].bind && target[ name ].bind( target ); 
  18.         }else
  19.           return target[ name ]; 
  20.         } 
  21.       } 
  22.     }); 
  23.   } 

對(duì)于每一個(gè)對(duì)象的實(shí)現(xiàn)這里不講細(xì)節(jié)了,有興趣可以看看我們的開源之后的代碼 :https://github.com/aliyun/alibabacloud-console-os/tree/master/packages/browser-vm

但是為了文檔能夠被加載在同一個(gè) DOM 樹上,對(duì)于 document,大部分的 DOM 操作的屬性和方法還是直接用的宿主瀏覽器中的 document 的屬性和方法。

由于子應(yīng)用有自己的沙箱環(huán)境,之前所有獨(dú)占式的資源現(xiàn)在都變成了應(yīng)用獨(dú)享(尤其是 location、history),所以子應(yīng)用也能同時(shí)被加載。并且對(duì)于一些變量,我們還能在 proxy 中設(shè)置一些訪問權(quán)限的事情,從而限制子應(yīng)用的能力,比如 Cookie, LocalStoage 讀寫。

當(dāng)這個(gè) iframe 被移除時(shí),寫在 window 的變量和設(shè)置的一些 timeout 時(shí)間也會(huì)一并被移除(當(dāng)然 DOM 事件需要沙箱記錄,然后在宿主中移除)。

總結(jié)一下,我們的沙箱可以做到如下的特性:

 

CSS 隔離

CSS 隔離方案相對(duì)來說比較常規(guī),常見的有:

  • CSS Module
  • 添加 CSS 的 namespace
  • Dynamic StyleSheet
  • Shadow DOM

CSS Module or CSS Namespace

通過修改基礎(chǔ)組件樣式前綴來實(shí)現(xiàn)框架和微應(yīng)用依賴基礎(chǔ)組件樣式的隔離性(依賴于工程上 CSS 的預(yù)處理器編譯和運(yùn)行時(shí)基礎(chǔ)組件庫配置),同時(shí)避免全局樣式的書寫(依賴于約定或工程 lint 手段)。

Dynamic StyleSheet

隔離方式是通過 JS 運(yùn)行時(shí)動(dòng)態(tài)加載卸載微應(yīng)用樣式表來避免樣式的沖突,局限性一是對(duì)于站點(diǎn)框架本身或其部件(header/menu/footer)與當(dāng)前運(yùn)行的微應(yīng)用間仍存在樣式?jīng)_突的可能性,二是沒有辦法支持多個(gè)微應(yīng)用同時(shí)運(yùn)行顯示的情況。

Shadow DOM

優(yōu)點(diǎn)是瀏覽器級(jí)別提供的樣式隔離能力,可以做到完全隔離。缺點(diǎn)在于,目前兼容性還是不太好,并且改造會(huì)涉及到舊應(yīng)用的業(yè)務(wù)代碼的改造,對(duì)子應(yīng)用侵入性比較高。

最終經(jīng)過實(shí)踐,我們選擇的方式是 CSS Module + 添加 CSS 的 namespace。CSS module 保證的是應(yīng)用業(yè)務(wù)樣式不沖突,Namespace 保證公共庫不沖突。我們實(shí)現(xiàn)了一個(gè) postcss 插件,會(huì)在應(yīng)用構(gòu)建的時(shí)候給所有的樣式都加上應(yīng)用前綴包括應(yīng)用公共庫的 CSS(這樣方便做到同一個(gè) 組件庫新舊版本樣式的兼容)。如下圖所示:

  1. // 宿主 host app 
  2. .next-btn { 
  3.     color: #eee; 
  4. // 子應(yīng)用 sub app 
  5. aliyun-slb .next-btn { 
  6.     color: #eee; 
  7. //宿主中生成的節(jié)點(diǎn) 
  8. <aliyun-slb> 
  9.     <!-- 子應(yīng)用的節(jié)點(diǎn) --> 
  10. </aliyun-slb> 

這樣實(shí)現(xiàn)的好處在于:

  • 每個(gè)應(yīng)用都有 namespace,可以多實(shí)例共存。
  • 不依賴特定的 CSS 預(yù)處理器。
  • 對(duì)于同一個(gè)庫不同版本的 CSS(如 fusion1 和 fusion2),可以做到徹底隔離。
  • 鑒于上面 JS 沙箱的存在,對(duì)于一些彈窗類的組件,這個(gè)微應(yīng)用獲取的 body 實(shí)際上是宿主生成的節(jié)點(diǎn),所以彈窗會(huì)被添加到微應(yīng)用的節(jié)點(diǎn)(也就是上面的 aliyun-slb)這個(gè)節(jié)點(diǎn),樣式不會(huì)失效。

不過也會(huì)有一些問題,比如:

  • 嵌套應(yīng)用組件樣式優(yōu)先級(jí)的問題。由于 CSS module 的存在,一般只會(huì)發(fā)生在公共 CSS 樣式中,這個(gè)就是只能盡量避免嵌套。
  • fusion 不同版本庫公用字體的問題。目前的解決辦法:比較 hack,使用工程化的手段替換掉 next 字體的名字。

如何和其他體系結(jié)合

如果看完上面的文章,覺得這個(gè)沙箱方案不錯(cuò),但是又已經(jīng)有自己的微前端體系了,想套用咋辦?

目前 ConsoleOS 的代碼已經(jīng)在 Github 上開源:http://github.com/aliyun/alibabacloud-console-os,這里不妨可以嘗試試用一下。

JS 沙箱部分

如果看懂了上面關(guān)于原理的介紹可以看到其實(shí)沙箱實(shí)現(xiàn)包括兩個(gè)層面:

  • 原生瀏覽器對(duì)象的模擬(Browser-VM)
  • 如何構(gòu)建一個(gè)閉包環(huán)境

Browser-VM 可以直接用起來,這部分完全是通用普適的。但是涉及到閉包構(gòu)建的這部分,每個(gè)微前端體系不太一致,可能需要改造,比如:

  1. import { createContext, removeContext } from '@alicloud/console-os-browser-vm'
  2. const context = await createContext(); 
  3. const run = window.eval(` 
  4.   (() => function({window, history, locaiton, document}) { 
  5.     window.test = 1; 
  6.   })() 
  7. `) 
  8. run(context); 
  9. console.log(context.window.test); 
  10. console.log(window.test); 
  11. // 操作虛擬化瀏覽器對(duì)象 
  12. context.history.pushState(nullnull'/test'); 
  13. context.locaiton.hash = 'foo' 
  14. // 銷毀一個(gè) context 
  15. await removeContext( context ); 

當(dāng)然可以直接選擇沙箱提供好的 evalScripts 方法:

  1. import { evalScripts } from '@alicloud/console-os-browser-vm'
  2. const context = evalScripts('window.test = 1;'
  3. console.log(window.test === undefined) // true 

CSS 沙箱

如果用 Webpack 構(gòu)建,可以直接配置如下:

  1. const postcssWrap = require('@alicloud/console-toolkit-plugin-os/lib/postcssWrap'
  2. // 下面是 webpack config 
  3.   test: /\.css$/, 
  4.   use: [ 
  5.     'style-loader'
  6.     { 
  7.       loader: 'postcss-loader'
  8.       options: { 
  9.         plugins: [ 
  10.           // 加入插件 
  11.           postcssWrap({ 
  12.             stackableRoot: '.prefix'
  13.             repeat: 1 
  14.             }) 
  15.         ], 
  16.       }, 
  17.     }, 
  18.     'css-loader'
  19.   ], 
  20.   exclude: /^node_modules$/, 

 

責(zé)任編輯:武曉燕 來源: 阿里技術(shù)
相關(guān)推薦

2024-07-16 11:26:35

微前端代碼JS

2021-01-26 10:33:45

前端開發(fā)技術(shù)

2022-01-17 11:41:50

前端Vite組件

2022-04-08 09:52:13

前端監(jiān)控系統(tǒng)

2017-12-12 15:24:32

Web Server單線程實(shí)現(xiàn)

2022-06-07 10:13:22

前端沙箱對(duì)象

2020-11-30 06:20:13

javascript

2020-04-02 09:31:49

微前端架構(gòu)系統(tǒng)

2023-02-26 01:37:57

goORM代碼

2023-03-01 09:39:40

調(diào)度系統(tǒng)

2020-08-17 08:20:16

iOSAOP框架

2019-03-22 08:25:47

沙箱網(wǎng)絡(luò)安全惡意軟件

2016-08-04 14:08:57

前端javascripthtml

2023-02-13 00:18:22

前端庫框架集合

2015-08-17 10:32:06

前端工程師優(yōu)秀

2015-08-24 09:02:49

前端工程師

2011-12-26 16:39:43

局部函數(shù)

2022-03-14 10:02:03

散列表鏈表哈希表

2022-10-20 11:00:52

SQL解析器

2016-09-28 17:34:27

JavaScriptvueWeb
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)