只討論架構,不討論框架。 提出除『微前端』以外另外一種建設
高彈性架構
的思路。javascript
由一羣儘量將數量最小化的軟件程序組成,他們負責提供、實現一個操做系統所須要的各類機制和功能
。這些最基礎的機制,包括了底層地址空間管理,線程管理,與進程間通信。前端
將系統的實現,與系統的基本操做規則區分開來。它實現的方式是將核心功能模塊化
,劃分紅幾個獨立的進程,各自運行,這些進程被稱爲服務。全部的服務進程,都運行在不一樣的地址空間。vue
讓服務各自獨立,能夠減小系統之間的耦合度,易於實現與除錯
,也能夠增進可移植性
。它能夠避免單一組件失效,而形成整個系統崩潰,內核只須要重啓這個組件,不至於影響其餘服務器的功能,使系統穩定度增長
。同時業務功能能夠視須要,抽換或新增某些服務進程,使功能更有彈性
。java
就代碼數量來看,通常來講,由於功能簡化,核心系統使用的代碼比集成式系統更少
。更少的代碼意味更少的潛藏程序bug
。react
微內核架構在使用時主要考慮兩個方面『核心系統』和『插件模塊』。應用邏輯被劃分爲獨立的『核心系統』和『插件模塊』,這樣就提供了良好的可擴展性
與靈活性
,應用的新特性和基礎業務邏輯也會被隔離
。webpack
核心系統一般是一個能夠獨立運行的最小化模塊
,操做系統(Windows NT、Mac OS X)就是這麼實現的。從商業應用的角度來看,核心系統爲那些特定的場景、規則、複雜的條件判斷提供了通用
的業務邏輯,而插件模塊則提供了更爲具體
的業務邏輯。能夠增長或擴展核心系統以達到產生附加的業務邏輯的能力。git
插件模塊一般是一個專業處理額外特性的獨立組件
。一般,插件模塊之間是沒有依賴的,固然你也能夠建立一個依賴其餘插件模塊的插件,但無論怎麼樣,讓插件模塊之間能夠彼此通信又不產生依賴是一個很重要的問題。github
核心系統須要知道每一個插件的可用性而且知道如何獲取它們,一個一般的實現方式是使用一組註冊表
。註冊表包括了每一個插件的基本信息,包括名稱
、數據規範
、遠程訪問協議(取決於插件模塊如何和核心繫統進行鏈接)
以及其餘自定義數據
。好比百度網盤中用於上傳文件的上傳插件提供了插件名稱、數據規範(輸入、輸出數據)、數據格式(json、xml),若是這個插件是經過異步進行加載的,那麼還會有一個具體遠程HTTP訪問協議地址。web
插件模塊能夠經過多種方式鏈接到核心系統,包括OSGI(open service gateway initiative)
、消息機制
、web服務
以及點對點的綁定(對象實例化,既依賴注入)
。使用何種方式主要取決於具體的應用場景和特殊需求(單機部署、分佈式部署),微內核架構默認沒有要求具體的實現方式,可是必須保證插件模塊之間不能產生任何依賴。npm
插件模塊和核心繫統之間的通訊規範分爲標準規範
和自定義規範
,自定義規範一般是指某個插件模塊是由第三方服務開發的。這種狀況下,就須要在自定義規範和標準規範之間提供一個Adapter,這樣核心系統就不須要關心每一個插件模塊的具體實現。在設計標準規範以前制定一個版本策略很重要。
核心系統提供了多種事件模式,主要包括經常使用的點對點模式、發佈訂閱模式。同時,事件的類型分爲全局(系統級)事件、系統內部事件以及插件模塊內部事件。因爲點對點模式中發送者和接收者之間沒有依賴關係而且一條消息只對應一個接收者,因此能夠用做廣播全局(系統級)事件,好比調起某個插件模塊。而發佈訂閱模式中訂閱者和發佈者之間存在時間上的依賴性,能夠用於系統內部事件和插件模塊的內部事件。此外,核心模塊也能夠經過發佈訂閱模式向外發佈某些屬於業務基本操做規則的事件。
當插件模塊註冊到核心系統以後,經過系統級事件能夠調起具體的某插件模塊。此時就須要核心模塊提供屬於基本操做規則的接口供插件模塊使用,一樣的,插件模塊也必須按照通訊規範提供運行入口(相似於java的Main方法)
和數據規範(參數格式,返回的數據格式)
,以此保障插件模塊能夠在覈心繫統上正確運行。插件模塊是獨立於核心系統以外的,可是根據具體的需求(提供單純的數據服務
、處理系統數據和信息
)可能會須要操做核心模塊的系統服務作一些定製化功能,此時核心系統須要提供一個上下文對象(Context),且插件模塊與外部進行交互只能經過此上下文對象。上下文對象提供了基礎操做(調起其餘插件模塊、調起系統服務、獲取系統信息)的API和事件。
把前端系統當成一個操做系統,業務基本操做的業務邏輯抽象成一個能夠獨立運做的系統內核,而不屬於業務基本操做的業務邏輯都當成一個應用程序,完成安裝、卸載、禁用、調用以及開機啓動等功能。
在功能愈來愈多,依賴愈來愈負責的大型前端系統中,若是在項目初期沒有很好地考慮後期兼容的靈活性、擴展性以及彈性,很容易出現項目難以維護或者誰都不想碰的尷尬場面,因此初期的設計很重要。
目前的大型前端單頁面系統使用的都是根據業務劃分獨立組件,進行解耦和複用,最後經過組件進行堆疊、編譯、上線。這樣雖然完成依賴良好的組件化設計考慮到了系統的擴展性和靈活性以及彈性,可是整個系統仍是牢牢綁在一塊兒的,並無根據基礎業務和附加業務進行很好的拆分。固然不少優秀的前端工程師也考慮到了這一方面,提出來微前端
的概念。不過微前端仍是一個比較新的技術概念,沒有通過不少大型前端系統的實踐。而微內核架構已經在操做系統和不少的產品的後端服務及前端APP中通過了不少的實踐。
上面提到核心模塊是一個能夠獨立運行起來,包含系統基本操做規則的最小化模塊。沒有任何插件模塊依然能夠正常運行並處理基本的業務邏輯,因此在大型前端系統中將基礎頁面以及基礎功能單獨包裝起來,組成一個最小化的模塊,稱之爲core system
。而這個core system
能夠經過包方式在多個系統間進行復用(NPM、bower、bundle、js chunk)。同時,將那些和業務相關的操做按照類型和場景封裝爲多個系統服務,並掛載(依賴注入)到核心系統中,稱之爲system service
。須要注意的是core system
可操做system service
,而system service
不可操做core system
。
此外,core system根據具體的模塊規範(AMD、CMD、CommonJS、ESM、SystemJS、UMD)向外部暴露了可交互的API和事件,稱之爲標準接口。後續在編寫插件模塊時要嚴格按照標準接口進行開發。
插件模塊是一個獨立於核心系統的專業處理不屬於系統基本操做的業務的模塊(組件),好比網盤中的上傳、下載、分享等功能。每一個插件模塊必須遵守定義好的標準接口
和通訊規範
進行開發,並且每一個插件模塊都是相互獨立的,因此沒有對每一個插件的實現細節作過多要求,如A插件模塊使用React開發,B插件模塊使用Vue開發,C模塊使用jQuery開發。
每一個插件模塊都應該提供一個包含本插件模塊簽名信息(Mainfest)
的JSON文件,簽名信息包括了這個插件的名稱、數據規範、依賴、遠程訪問地址(異步加載的js下載地址)和其餘自定義字段。在前端加載核心系統時將該Mainfest文件註冊進去,完成核心繫統和插件模塊的鏈接。
每一個插件模塊都應該提供一個統一名稱的運行入口,好比start方法。也能夠按照標準接口提供插件的生命週期事件,方便更細粒度的控制。
每一個插件都提供了各自的Mainfest簽名文件
和可執行文件(JS文件、CSS文件)
。因此當服務器接收到瀏覽器請求時能夠將所要求的插件Manifest進行merge,合併成一個大的JSON結構,而後返回給瀏覽器。瀏覽器接收後,執行核心系統並註冊Manfiest信息,而後啓動。在註冊過程當中能夠按照需求完成開機啓動(默認執行)
、預加載
以及後臺運行
等不一樣類型的操做。
在業務邏輯和插件內部邏輯中能夠能存在調起其餘插件模塊的需求,因爲插件模塊之間不產生依賴而且獨立於核心系統,因此沒法直接進行調起。不過因爲註冊表
和點對點事件模式
的存在,能夠經過核心系統向外暴露的API傳入插件名稱和組
、插件模塊ID
等信息進行調起。在調起以前先判斷該插件在是否已註冊,是否已加載(同步加載、異步加載),是否爲單例和互斥,參數信息和數據格式,保證它能夠正確的調起。
在插件運行過程當中出現異常時,經過系統級事件通知核心模塊。核心模塊根據簽名信息中的標識選擇重啓
或關閉
該插件模塊。
在複雜的前端系統中同一個功能可能會存在過個入口的狀況,好比上傳、下載、分享等功能都是經過不一樣位置的按鈕點擊進行調起。一般,將具體功能(插件模塊)
和插件模塊入口的UI展現
進行隔離。首先,在基礎頁面結構中按照需求進行分塊
,分紅不一樣的功能塊,如菜單欄、右鍵菜單、列表項、右側區域、左側區域,併爲這些區域定義惟一的名稱和ID
。在須要進行入口展現的插件模塊的Manifest
中,標識入口的區域和展現方式(按鈕、圖片、引導塊、菜單項、下拉菜單)。
核心系統在註冊表註冊完畢後,解析那些須要展現入口的的字段並交給專門渲染插件模塊入口
的系統服務
,這樣就經過配置完成了多入口的管理,在後續需求變更和修改時,只須要更改Manifest
文件便可,更加完善了系統的擴展性、靈活性、彈性。
架構是獨立於框架和類庫的存在。
微內核架構的核心就是使業務的基本操做和專業處理額外特性的操做相隔離,提升系統的擴展性、靈活性和彈性。因此在技術選型時咱們須要考慮三個方面:核心系統、系統服務、插件模塊。
核心系統一般包含一個項目所須要的基本功能,包括基本的展現頁面、交互操做、業務處理,代碼量一般不多;系統服務提供業務處理的通用功能,好比列表操做、彈框、提示、異步化接口處理等,一般將系統中通用的需求抽象到這一層中;因此,這兩個方面可使用目前常見的react或vue經過webpack工具進行規範化開發,但如何向外部暴露核心系統的API和事件給插件模塊調用是一個十分重要的問題。
插件模塊更傾向於一個專業處理額外特性的lib庫,因此推薦使用rollup或者webpack的lib模式進行開發和打包,產出一個『乾淨』的bundle(也能夠發佈到NPM中,實現獨立發佈和維護)。須要注意的是,若是這個bundle按照定義好的標準規範進行開發,那麼它能夠在任意一個微內核架構下運行,達到跨系統的能力。就像按照X86規範編寫的程序能夠在任意一個X86架構的系統上運行同樣。
方案一:source code 插件模塊的代碼放置在一個根文件夾中,經過源代碼進行開發和編譯。每次更改後經過rollup或webpack產出一個bundle與Manifest文件,而後將它們上線更新便可。
這種模式下,插件模塊的代碼更新後,對應的Manifest文件也會更新,因此核心系統加載到插件模塊也會被更新,不須要基礎業務邏輯執行任何操做。 優勢:不須要更新並上線基礎業務代碼。 缺點:沒有
版本號
的管理功能以及不方便測試。
方案二:npm install 插件模塊發佈到github、gitlab等其餘託管平臺中,經過npm進行安裝到基礎業務邏輯中。插件模塊每次更改後須要從新發布到託管平臺,並在須要在業務邏輯中更新版本號從新執行npm install xxx
,而後從新編譯業務代碼進行上線。
插件模塊更新後,不須要像方案一那樣上線插件模塊。而是更新業務邏輯的依賴,安裝最新版本的插件模塊。 優勢:能夠經過版本號加載不一樣的階段的插件模塊以及方便測試。 缺點:更改後須要從新安裝插件模塊,並對依賴此插件模塊的業務邏輯從新進行編譯和上線。迴歸成本大,除了迴歸插件模塊還要回歸其餘基礎業務邏輯(固然也能夠像方案一那樣作,可是這樣就拋棄了npm的最大優勢 -> 版本號管理)。
方案一:服務器渲染直出到HTML中 服務器收到瀏覽器的頁面請求時,將該頁面須要的插件模塊的Manifest簽名文件進行Merge操做,而後統一輸出到HTML中並返回給瀏覽器。
方案二:經過異步化獲取 經過script標籤的async和defer功能或AJAX,異步從服務器獲取Merge以後的Manifest簽名信息集合。
核心系統調起插件模塊時,能夠經過插件聲明的遠程訪問協議的HTTP地址,進行異步加載。
方案一:Manifest簽名文件 在Manifest簽名信息中放置插件模塊的遠程訪問協議
,好比上傳插件模塊的簽名示例:
{
// 插件名稱
"name": "upload",
// 組
"group": "com.xxx.xxx",
// 預加載插件模塊資源
"preload": true,
// 數據規範,要求輸入的參數
"arguments": {
// 核心系統提供的上下文對象
"ctx": {
"type": "Object",
"required": true
},
// 須要上傳的文件信息
"file": {
"type": "Object",
"required": false
}
},
// 遠程訪問協議
"entrance": "http://www.a.com/static/plugin-bundles/upload-0.0.1.min.js"
}
複製代碼
方案二:異步化接口 + import()
該方案是系統插件模塊的遠程訪問協議不放置在插件模塊的Manifest中,而是額外經過異步化接口請求獲得遠程訪問協議。而後經過webpack提供的require.ensure()或esm的import()加載插件資源。
// ctx爲核心系統上下文對象
ctx.loadPlugInAdapter = (pluginName, groud) => {
// 經過接口請求上傳插件模塊的遠程訪問協議
fetchEntrance(pluginName, group).then(url => {
// 核心系統執行插件模塊
ctx.invoke(pluginName, url);
});
}
// 調起插件模塊
ctx.loadPlugInAdapter('upload', 'com.xxx.xxx');
複製代碼
架構和框架是獨立的,本文僅僅是提出一種架構思路,並且這個架構也在百度的某款用戶量很大的複雜前端產品中得以應用。基於這一套彈性架構並結合Vue/React的現代化開發理念,能夠很好的完成高複雜度的前端系統。但願本文能夠給大家提供了除微前端
以外的構建高彈性
前端系統的另一種思路。