微前端框架設計

導讀


無論你們有意或者無心間,或多或少都已經接觸到了微前端這個新的概念,這種新的前端架構真的有存在的必要嗎?畢竟沒有銀彈,在新的架構體系下,它除了帶了好處之外,同時也帶來了風險和技術挑戰。本文將帶你揭祕微前端給現有的工程化體系帶來了的意義,和它的架構設計,以及簡單講解一下微前端的核心模塊loader、sandbox。

微前端是一種相似於微服務的架構,它經過將一個單體的web應用拆分紅多個子應用,在運行時經過主應用來加載對應子應用來達到解耦子應用單獨運行、開發、部署的目的。
css

爲何須要微前端


咱們先來了解一下微前端出現的緣由,因爲大部分開發同窗開發的都是單體應用。先舉幾個實際工做中開發單體應用常見的問題吧:
html

  • 同窗A跟我吐槽:
    • 場景:
      • 最近要接受一個遺留後臺系統,裏面的技術老舊不說,連框架都是本身造的輪子,文檔也沒有
    • 問題:
      • 近期可能有一些新功能要增長到舊系統內,可是在原有系統上改造難度高
      • 後續可能會對舊系統內的現有功能作一些改造,可是原有的一套技術體系老舊,想要重構到ts+react+webpack,可是裏面有些祖傳頁面大機率不會迭代,又必須保持線上
  • 同窗B團隊近期也遇到了問題:
    • 場景:
      • 他們所在的團隊要負責一個運營後臺系統,目前系統內的功能已經很是龐大,涉及的成員也很是多,裏面每個子菜單的功能都很是龐大,每一個子菜單可能都是因爲不一樣的團隊在負責。而且目前全部功能所有聚合在一個項目內
    • 問題:
      • 項目內的代碼規範和技術體系選擇,都會形成不一樣團隊的爭議
      • 某個子菜單內的功能進行了變動整個項目都須要從新打包部署,增長了不穩定性和分支混亂,部署時間長
      • 不一樣子菜單內同時有需求迭代,二者的功能須要進行合併才能部署到預發、線下,這就形成了不一樣需求之間的互相擠兌,影響測試發佈效率,並且還須要保證相互之間沒有影響


在前端應用日益複雜化,框架技術更新迭代快的場景下,現有的單體工程化方案在多團隊協做、解決歷史遺留代碼力不從心,微前端工程化方案很好的解決了上述問題:技術棧無關、拆解巨石應用。

微前端的核心價值前端

  • 拆解巨石應用爲多個子應用
    • 子應用獨立運行和發佈
    • 增量升級
  • 技術棧無關
  • 沙盒隔離子應用


經過微前端的核心價值咱們能夠很好的解決上述問題:歷史遺留代碼、跨多個團隊協做、發佈效率問題。跨空間和時間的產品極可能會致使應用變成一個巨石應用、技術棧老舊,致使項目的維護成本變高,微前端就是爲了解決這些問題。

在簡單瞭解了現有工程體系的問題,還有微前端能解決的問題以後,咱們來簡單瞭解一下微前端的技術架構。
react

微前端的技術架構

why not iframewebpack


其實從瀏覽器原生的方案來講,iframe不從體驗角度上來看幾乎是最可靠的微前端方案了,主應用經過iframe來加載子應用,iframe自帶的樣式、環境隔離機制使得它具有自然的沙盒機制,但也是因爲它的隔離性對用於體驗帶來了一些反作用:
web

  • 視窗大小不一樣步(例如咱們在iframe內的彈窗想要居中展現)
  • 登陸態cookie同步問題
  • 子應用間通訊問題
  • 大量組件重複

SPA微前端架構前端工程化

從iframe的用戶體驗咱們很難將其做爲微前端的標準方案,那咱們本身要作一套微前端框架具體要作哪些事情呢,從iframe功能咱們大體可以瞭解到,微服務框架須要具有如下幾個功能:
瀏覽器

  • 子應用加載器(Loader)
  • 路由控制(Router)
  • 沙盒隔離(Sandbox)
  • 子應用通訊(Store)

主工程基座緩存

微前端架構必須有一個主工程基座,基座主要是作爲承載子應用的容器,子應用經過導出對應的格式,主應用在進入到對應路由時加載對應的子應用。

前端框架

微前端核心功能

Loader

loader是微前端核心模塊的加載器,能夠經過loader來進行子應用的加載,目前的微前端方案設計裏面通常有兩種模式。

第一種是非侵入式,經過加載對應子應用的 index.html 文件,再經過對首頁html文件進行解析,獲取到子應用的js文件和css文件,進行加載。

另外一種是子應用打包成一個js文件,按照規範的導出格式,主應用只加載 index.js 文件。獲取到對應的render和destroy方法。

let vm;
module.exports = {  render () {  vm = new Vue({  render: (h) => h(Home),  }).$mount(dom);  },  destroy() {  vm.$destroy();  } } 複製代碼


做爲模塊加載器它一般須要提供如下幾種能力:

  • 下載子應用源碼
  • 編譯解析子應用導出內容
  • 將公共依賴注入到子應用中

External

在微前端中有一個須要解決的問題就是,子應用間的公共依賴,咱們如何抽離項目間的公共依賴呢,因爲咱們將一個應用拆分紅了多個子應用,那子應用之間的依賴如何複用。若是瞭解commonJS的同窗應該知道,commonJS具有加載模塊緩存能力,加載過的模塊會將其緩存起來,那麼是否是咱們能夠將子模塊以commonJS的規範進行打包。在加載子模塊時,提供全局的exports和require方法,將子應用導出的exports進行收集,在require時加載咱們配置的external資源。

commonJS加載實現

Module._catcheModule = {}
function req(moduleId){  if(Module._catcheModule[p]){  //模塊已存在  return Module._catcheModule[p].exports  }  //沒有緩存就生成一個  let module = new Module(p);  Module._catcheModule[p] = module;  //加載模塊  module.exports = module.load(p);  return module.exports; } function Module(p){  this.id = p  this.exports = {}  this.load = function(filepath){  return Module._extensions(this)  } } Module._wrapper = ['(function(exports,require,module,__dirname,__filename){','\n})'] Module._extensions = function(module){  let fn = Module._wrapper[0] + fs.readFileSync(module.id,'utf8') + Module._wrapper[1]  vm.runInThisContext(fn).call(module.exports,module.exports,req,module)  return module.exports } 複製代碼


經過上面commonJS的源碼模式實現,咱們只須要將exports中增長公共依賴,而且子應用經過webpack構建工具,提供external配置一樣的公共依賴便可。

Sandbox


在微前端框架中另外一個核心的模塊就是沙盒,因爲多個子應用會反覆的展現在同一個容器內,子應用中不免會形成對當前環境的反作用,例如:全局樣式、全局變量、監聽事件、定時器等。沙盒在這裏主要是爲運行中的程序提供隔離環境,避免應用之間相互影響。

在web端設計沙盒咱們須要考慮哪些因素

  • 全局環境污染
  • 事件污染
  • style污染
  • 定時器污染
  • localstorage污染


爲解決全局環境污染和style污染,一般採用,快照模式和代理劫持模式。

快照模式

  • 沙盒啓動時
    • 遍歷存儲當前全局環境變量,將其緩存
    • 遍歷head全部標籤,將其緩存
  • 沙盒運行時
    • 執行反作用程序
  • 沙盒關閉
    • 遍歷緩存遍歷將其與全局環境對比,發現差別進行還原
    • 將當前head標籤與緩存標籤進行對比,發現差別進行還原

代理模式

  • 沙盒啓動時
    • 緩存原始節點添加、刪除、在節點前插入等原始方法
    • 緩存添加監聽事件、移除監聽事件方法
    • 緩存添加定時器事件、移除定時器事件
    • 將緩存的事件更改成代理事件,在代理事件內部執行真實事件前,收集變動
  • 沙盒運行時
    • 執行反作用程序
  • 沙盒關閉
    • 恢復沙盒運行期間產生的變動
    • 移除代理事件

最後

招聘


另外也但願可以吸引更多優秀的同窗加入咱們,加入字節跳動。

團隊相關介紹:ByteDance Web Infra Team is hiring

可經過:zhouxiao.shaw@bytedance.com 進行投遞
相關文章
相關標籤/搜索