首頁>技術>

原文地址:/file/2020/09/20/20200920035050_6.jpg 多個應用間有相同依賴,可以是模組、可以是路由、可以是業務元件。

3、微前端的應用場景

什麼場景可以考慮引用微前端架構?

a)App大且含有多條業邏輯;

b) App是對應到不同的組織架構中;

c)多App間複用邏輯較多且有組合入口出現;

d) 不斷迭代的App。

4、正規化圖

正規化圖

基座式的微前端架構基本是這樣一個簡單邏輯組成,基座工程 + 子應用*n。

基於 Vue的基座式微前端

1、執行時

有了對微前端的基本認識,我們梳理一下微前端專案在執行時基座與子應用的基本協作流程圖:

執行流程圖

執行時基座扮演組織者角色,因此基座應用應具備以下特點:

a)按需載入子應用靜態資源。

b) 動態註冊子應用,並註冊子應用關鍵模組:router、store等。

2、部署時

我們強調子應用的自治,所以子應用部署應該是獨立的。

部署流程圖

在子應用部署測試和生產環境的時候,都應該將註冊資訊同步反映到登錄檔上。登錄檔模組是解耦基座和子應用的關鍵,它可以簡單的是一個json檔案,也可以複雜的是一個子應用管理服務,具體尺度根據業務來衡量。由於我們的子應用數量及部署更新頻率還不高,所以先簡化登錄檔模組,以檔案加手動更新方式來實現。

登錄檔模組應該具備以下特點:

a)資訊對映:維護全量子應用註冊資訊,部署時間、版本、靜態資源、scopeID等。

b) 動態性,隨著不斷部署來更新登錄檔資訊。

c)可讀性,暴露一個可讀的物件或返回物件的自執行函式。

我們期望降低子應用開發者的心智負擔,因此後續會將登錄檔模組服務化並接入上線系統部署鉤子,用來管理子應用的資訊。

3、開發時

開發時基座與子應用的協作是相對複雜的,並且也包含了執行時的基本邏輯,下文將圍繞開發狀態下各技術點展開闡述。

注意:此處是給出的是實現思路,具體程式碼實現細節可自行實現。

1、基座執行環境

開發時基座的執行環境有以下兩種方式。

a) 本地服務式 - 子應用開發者將基座拉取到本地,執行到本地服務中。

本地服務式是將基座和子應用放置於子應用開發者本地,由於同處於一個物理空間(磁碟)上,基座可以通過直接引用子應用的構建出口來解決引用熱更新問題。這種做法解耦不是很徹底,需要子應用開發者本地啟動基座服務,並且沒法提供多個子應用同週期協調開發的環境。

b) 集中服務式 - 將基座維護在一個專供開發時應用的伺服器容器中。

集中服務式開發體驗較好且可以多方協調開發,但需要維護專供開發時的服務節點,並通過網路解決基座與子應用間的資源載入及熱更新問題,設計難度較大,由於是網路傳輸資源所以耗時需要優化。

2、選型

為快速落地框架,我們選用本地服務式方案。下面主要介紹本地服務式各點實現。不過為了後續轉集中服務式做準備,這裡也會列出一部分實現思路。為提高程式碼可讀性,我們命名基座為Voo。

開發時-本地服務式關係圖

a) 子應用入口檔案的包裝
export default (Voo) => { Voo.Vue.prototype[appName] =vuePrototypeExtension return { router, store, App }}釋:子應用暴露一個方法並返回router、store、App模組,供基座註冊呼叫和回傳基座物件(Voo);原型擴充套件方法需要增加namespace,方便提供給子應用本身和其他應用複用。

b) 子應用路由、資料流的包裝

export default [ { 'path': '/', 'redirect': 'home' }, { 'path': 'home', 'name': 'Home', 'component': Home }]釋:暴露一個routes陣列,供基座動態插入,由於基座與子應用執行在同一個router物件中,所以遵循“以 / 開頭的巢狀路徑會被當作根路徑。這可以讓你充分的使用巢狀元件而無須設定巢狀的路徑”的規則。export default {state, mutations, actions, modules }釋:暴露一個store module物件,供基座動態註冊。
c) 子應用腳手架的修改
webpackConfig.output.library ='[name]'webpackConfig.output.libraryTarget= 'umd'釋:修改輸出目標為umd便於runtime、開發時基座的引用webpackConfig.output.jsonpFunction= appName釋:如果在同一網頁中使用了多個來自不同編譯過程(compilation)的webpack runtime,則需要修改此選項。
webpackConfig.plugins.push(newwebpack.BannerPlugin({  'banner': '/* eslint-disable */', 'raw': true}))釋:向bundle中追加eslint註釋,防止基座eslint校驗不通過。'devServer': { 'writeToDisk': true, before(app,server) { request.post('http://localhost:7777/__dev_subApp_register', { 'form': { 'id': [appName], 'resourcePath':path.resolve(__dirname, `dist/${[appName]}.js`) } }, (error, response, body) => { if (error) { console.error('[ error ]請先啟動基座工程Voo') } else {console.log(body)} }) }}釋:將構建目標寫入到物理磁碟中,devServer預設是寫入在虛擬記憶體中的,基座工程無法import。

在devServer啟動前向基座工程的本地服務傳送構建目標的物理路徑,注意:此處路徑是磁碟絕對路徑,是可以通過import載入的。` 7777`是基座工程固定的埠,__dev_subApp_register是基座工程固定的子應用註冊路由。

d) 基座腳手架的修改
'devServer': { before(app, server) {   app.post('/__dev_subApp_register',(req, res) => { const params =Object.assign(req.query, req.body) const devSubAppRegisterInfo = `/* eslint-disable */export default (regiestSubApp, opts) => {import('${params.resourcePath}').then((res) => { const subApp =res.default(opts) regiestSubApp({ id: '${params.id}', subApp })})}`  fs.writeFileSync(`${__dirname}/__dev__subApp_register_info.js`,devSubAppRegisterInfo) res.json({ 'code': 0, 'message': '開發時註冊成功' }) }) }}釋:編寫子應用註冊介面,在接收到子應用的註冊請求後,將基座引用邏輯寫入到__dev__subApp_register_info.js。

4、集中服務式

開發時-集中服務式關係圖

集中服務式,設計中需要注意以下幾個點:

a) 配置服務,將nginx反向代理到基座服務;

b) 基座服務提供與子應用互動的介面,此處我們選用webpack的devServer進行描述。

1、在基座根目錄建立 /subApps。

2、在before編寫註冊介面。當註冊請求進入後將子應用/dist檔案寫入到/subApps中,如果/dist檔案太大,可以採用壓縮解壓,如果子應用資料夾存在則更新。

c) /subApps目錄結構

├── App.vue

├── main.js

├── ...其他目錄

├── subApps

├── appA

│ ├── appA.js

│ ├── ...各chunk

│ └── appA.css

├── appB

│ ├── appB.js

│ ├── ...各chunk

│ └── appB.css

├── ...其他subApp

└── index.js

d) 動態讀取 /subApps下所有檔案,暴露出去
const requireSubApps =require.context('./', true, /\\.js|.css$/)export defaultrequireSubApps.keys().map((fileName) => { return requireSubApps(fileName).default})
e) 熱更新

由於基座入口引用的是基座服務本地檔案,所以,我們只需要在子應用程式碼發生改變時觸發基座註冊介面就行。實現如下:

before(app, server) {registe();app.post('/__dev_update', (req, res) => {registe()})}

由於需要監聽main.js入口以內所有模組的變化,所以將監聽邏輯放到main.div.js,程式碼實現如下:

if (module.hot) { module.hot.accept('./main.js', ()=> { fetch('/__dev_update') });}同時修改dev和prod環境下的打包入口。configureWebpack(webpackConfig){ webpackConfig.entry =process.env.NODE_ENV === 'development' ? { [appName]: ['./main.dev.js'] } : { [appName]: ['./src/main.js'] }}
f) 優化

熱更新時按需上傳[hash].hot-updage.json,降低網路耗時。

4、聯調|測試

聯調階段:需要關注的問題是mock、proxy,這兩點都可以沿用spa應用原有的開發方案。

測試階段:基座和子應用都是通過上線系統管理所以可以利用其提供的測試環境。關鍵需要注意一下注冊表的測試環境提供。

5、關鍵點

1、子應用生命週期

由於子應用會被當做一個路由元件註冊到基座中,所以子應用可以利用其root元件的vue生命週期。

2、沙盒化router、store、css、vue原型擴充套件

路由,將子應用註冊到其ID為根的路由上,並將其暴露的路由註冊到children上。

Voo.$router.addRoutes([ {'path': `/${ subApp.id}`, 'children': subApp.router, 'component': subApp.App}])

Store module, 動態註冊的store module本來就是具有作用域的,依照vuex文件即可。

Voo.$store.registerModule(id,{ 'namespaced': true, ...subApp.store })。

Css module, 通過postcss給子應用追加作用空間。

constpostcssNamespaceGlobal = postcss.plugin('postcss-namespace-global', ({namespace= ''}) => (root) => { root.walk((node) => { if(node.selector){ node.selector =(node.selector.split(',').map((selector) => { if(selector.match(/^(\\s*)(html|body)(\\s*)$/)) {  return selector } return `${namespace}${selector}` }).join(',')) } })})module.exports ={'plugins': [postcssNamespaceGlobal({namespace: `.${appName}`})]}

Vue原型擴充套件,給子應用用到的原型擴充套件方法規定到其ID對應的物件中。

3、 複用層

複用層比較複雜。有著較多種類的使用場景。下面分析一下在微前端架構中會出現哪些複用的東西,怎麼去選型及管理。總體來說複用層的內容可以分為兩種:

a) 類性質 。呼叫時建立例項,因此runtime時互不干擾。但要根據是否需要鎖定版本來確定複用內容的管理方案。

需要鎖定版本,採用npm scope,管理在公司內部的npm服務上。注意:要規範好子應用npm安裝重複問題。

不需要鎖定版本,採用全域性註冊,例如全域性註冊的業務元件。注意:子應用註冊需要scope。

b) 函式性質。 呼叫和執行是同一組程式碼,這種複用內容性質是脆弱的。所以對設計者要求較高,且迭代應向下相容。

4、 複用內容

a) UI元件庫等第三方依賴:類性質,在基座中規定並回傳給子應用。

b) ajax庫及統一介面處理:函式性質,在基座中規定並回傳給子應用。

c) 業務類元件:類、函式性質,採用全域性註冊回傳給子應用。

d) 子應用特色業務元件:如chart、workflow等:類性質。託管npm。

5、 子應用拆分粒度

太粗、太細的子應用粒度都不利於App的維護,所以要根據業務和組織架構合理拆分。我們可以參考兩個原則:與服務對應形成前端微服務化、與團隊對應。

6、Q/A

Q: 在“開發時-本地服務式”中,既然是在本地啟動基座和子應用,為什麼基座不直接import子應用入口而是import子應用的bundle。

A: 因為框架的目的是儘量解耦應用,如果直接import子應用入口,那麼子應用就相當於是在基座環境中構建的。這樣即增加了子應用開發、生產兩個環境的構建差異,又限定了子應用的開發依賴。

總結和展望1、總體來說這一套架構解決了以下問題:

a) 解決業務繁多的專案分治;

b) 多團隊協作開發,且團隊內專案自治;

c) 對敏捷迭代的專案構建良好的基礎;

d) 子應用開發者無需關注基座及其歷史子應用業務,直接依賴基座預覽開發效果,體驗提升;

e) 子應用開發幾乎無異於SPA,無學習門檻。

2、思考

此次是我們在微前端道路上的初探,輸出也只是基於Vue技術棧的單一形態。所以圍繞微前端概念我們還有很多事情要做。

在設計此架構前我也調研過很多應用微前端的文章,得出的結論竟然讓我自己覺得有些矛盾。微前端的理念是為了解耦,但是往往很多使用者還希望通過微前端實現業務的高度複用。那麼矛盾來了,複用就伴隨著耦合。所以說沒有銀彈,我們要做的是解耦子應用的同時,儘可能的對複用層進行分類管理,結合業務場景定製化適合的微前端架構。

3、優勢

a) 基於同技術棧的微前端,可以快速抽離複用層並無侵入性的投入使用;

b)基於vue,有效的利用了store、router動態註冊特性,貼合58商業目前技術棧及存量專案;

c) 在本地式開發流程中子應用腳手架和基座腳手架之間的合作可以提供穩定的熱更新方案。

4、規劃

將目光放的再長遠一些,那麼我們還應該做以下規劃。

d) 子應用跨技術棧,解耦更徹底,讓微前端能應用到更大的聚合App上和組織架構中,當然複用層將變為一個挑戰。

e) 為增加開發體驗,基座採用集中服務化方案,例如:有子應用需求接入時就將基座部署到沙箱節點上,或者可以將基座應用設計為服務端渲染並提供一套開發專用帶許可權的介面。這樣就可以解決專門為開發提供服務的問題,同時還可以封裝登錄檔相關邏輯以管理子應用。

f) 登錄檔模組服務化,此項主要是為規範工程化管理。

作者簡介:

張軍,58集團前端工程師。

參考文獻:

康威定律:http://www.melconway.com/Home/Conways_Law.html

原文地址:https://mp.weixin.qq.com/s?__biz=MzI1NDc5MzIxMw==&mid=2247489620&idx=1&sn=1f5dbe48475c037bf20fe1cdb81c78de

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Python | Flask 解決跨域問題