导入第三方组件_大型 web 应用公共组件架构是如何来的?
來源:騰訊AlloyTeam
https://mp.weixin.qq.com/s/gVUJRF_nLHOT_iXDXQ8F-w
騰訊文檔公共組件歷史包袱
1. 架構(gòu)問題——開發(fā)層面
騰訊文檔管理的公共組件, 設(shè)計之初,采用了各種便于快速迭代的設(shè)計方式,組件代碼結(jié)構(gòu)和規(guī)范也缺乏統(tǒng)一,在長期的開發(fā)過程中質(zhì)量沒有得到保障。隨著需求不斷累積,目前存在比較大的歷史包袱。大量組件錯綜復(fù)雜,相互輯合緊密,而導(dǎo)致不管多么小的改動都需要數(shù)天的惡戰(zhàn)才能完,對于開發(fā)新功能和修復(fù)缺陷的同時時,都異常痛苦。主要存在的問題是以下幾點。
1.1 難以預(yù)料第三方公共組件導(dǎo)致的卡頓
騰訊文檔管理的公共組件(以下稱FC)主要通過 script-loader 動態(tài)加載承載了各個頁面的公共業(yè)務(wù)邏輯,然后將腳本注入到品類的 HTML 中,比如登陸、分享,權(quán)限等。這些邏輯都是同一個線程中執(zhí)行的。
第三方組件是由不同團隊和開發(fā)人員在維護著,往往有著不可控制的預(yù)期,品類方難以保證引入某一個組件的性能是否合理,從而容易導(dǎo)致品類編輯發(fā)生卡頓,及性能數(shù)據(jù)下降。
目前在 excel 調(diào)用公共組件過程中,會手動停止卡頓監(jiān)控,從而讓公共組件邏輯不影響詳情頁的卡頓數(shù)據(jù)。然而,這無法從根本上改變用戶主進程卡頓的體驗問題。
// 以下偽代碼async loadModule(name){ ? ?// 卡頓監(jiān)控停止 ? ?jank.stopReport(); ? ?await dosomeThingToLoadModule(name); ? ?jank.restartReport();}
【案例】 打開權(quán)限組件 cpu 暴漲,表格卡頓。
1.2 script-loader 加載形式鏈路非常長,公共組價加載異常延遲。
首先需要加載 assets.json 依賴映射文件,然后再異步加載需要功能的 js 代碼,最后再初始化組件,向后臺請求組件所需數(shù)據(jù),進行渲染,最終才能完整展示。這是一個非常長的鏈路,導(dǎo)致用戶使用體驗相關(guān)功能非常耗時。
1.3 發(fā)布沒有版本控制。
“一次更新,多端升級” 本來是 FC 設(shè)計之初的一種考慮,但在日積月累的迭代中,我們積累了無數(shù) bug,每一次常規(guī)發(fā)布之夜都伴隨著驚恐與噩夢。由于模塊A 發(fā)布修復(fù)了某個 ppt 的 bug,帶了某個 word 的新 bug; 由于某一個版本的升級,帶來全品類功能的崩潰。缺乏版本控制的后果就是,為了節(jié)省半個小時的包升級時間,帶來了大量調(diào)用品類方之間的缺陷連鎖反應(yīng)。我們的設(shè)計目標除了盡可能保證發(fā)布效率,發(fā)布的質(zhì)量和穩(wěn)定性也是非常重要的。
1.4 組件調(diào)用形式不規(guī)范和統(tǒng)一
// 以下偽代碼 // 業(yè)務(wù)A const someModule = await loadModule('someModule'); someModule.init({ ? ?xxx: 'yyyy', ? ?zzz: 'hello', ? ?from: 'xxx' })
// 以下偽代碼// 業(yè)務(wù)B const someModule = await loadModule('someModule'); someModule.init({ ? ?bbb: 'yyyy', ? ?ccc: 'hello' })
公共組件沒有統(tǒng)一的入?yún)⒁?guī)范。每次開發(fā)的步驟是,在品類 A 已經(jīng)提前接入前提某組件下,品類 B、C直接復(fù)制黏貼過去,然后完事。由此帶來的問題是:我們發(fā)現(xiàn)大量由于品類直接差異性導(dǎo)致的公共組件 bug 。
1.5 通信機制混亂
script-loader 即承擔了模塊加載的職責(zé),內(nèi)部有又事件通信的邏輯。而公共組件和各個品類的通信除了使用SLR.listen 外,同時又摻雜 window.addEventListener,導(dǎo)致很多地方重復(fù)監(jiān)聽,同時在定位問題時帶來了困擾。示例:excel 和 word 對應(yīng)的通信不一樣。
// 偽代碼window.something.listen('someEvent', ()=>{})
// 偽代碼document.addEventlistener.listen('someEvent', ()=>{})
1.6 內(nèi)部大量使用全局變量
FC 倉庫僅 xxx 這個變量就有500 多處調(diào)用方。公共組件使用全局變量容易會造成對詳情頁的污染,同時讓組件邏輯與品類的特定變量耦合,一旦某一個品類對應(yīng)的字段在迭代中發(fā)生變化,就會造成意外 bug 。
2. 架構(gòu)問題——產(chǎn)品層面
架構(gòu)的不合理設(shè)計,會帶來一些很大的負面影響,尤其是在需求的開發(fā)周期上。這本身是一個惡性循環(huán):
解決方案思考
綜上所述,我們可以發(fā)現(xiàn),目前我們原來對第三方公用組件的設(shè)計思路是——把公用組件當作編輯頁不可或缺的耦合部分。實際上,公共組件,例如,權(quán)限,分享,通知等功能,具備獨立應(yīng)用的功能,它們應(yīng)該更像是一個可拔插的插件,品類不應(yīng)該關(guān)心插件的內(nèi)部細節(jié),插件也不應(yīng)該有權(quán)限影響和破壞外部主進程。讓每次變更都變得可控,并且避免缺陷,同時最大程度地滿足功能性和靈活性的要求是這次架構(gòu)設(shè)計的目標。
解決方案是建設(shè)可拔插式插件化公共組件體系。定制標準的插件化規(guī)范,可便于拓展成對第三方開發(fā)者開發(fā)插件的體系。而 FC 公共組件是作為官方內(nèi)置插件的形式存在。插件體系有幾個比較關(guān)鍵的點:第一是,第三方插件質(zhì)量會參差不齊,如何約束插件的運行不會導(dǎo)致頁面的卡頓。第二點是,插件如何調(diào)用文檔SDK,也即使如何規(guī)范插件和主線程的通信問題。第三點是,插件安裝,卸載等后臺管理服務(wù)。
2.1 如何安全的運行插件?
2.1.1 插件類型
首先我們的插件體系分為兩類:純計算邏輯型插件 和 UI 交互式插件。純計算邏輯插件,比如一個自定義函數(shù),一個自定義任務(wù)等。這種插件可以通過使用 web worker 進行多線程計算進行隔離。UI 交互式插件,比如分享彈窗,權(quán)限側(cè)邊欄等,目前 FC 公共組件全部是這種類型。這種插件需要復(fù)雜的 UI 交互,我們可以通過 chrome 的 site-isolation 特性(參考第三方 web 應(yīng)用進程隔離),用不同域的域名動態(tài)創(chuàng)建 iframe,對應(yīng)的 iframe 內(nèi)容區(qū)域會和主進程進行隔離,從而保證品類的性能和安全性。
2.2 插件如何與主進程通信
出于安全限制,插件不應(yīng)該直接訪問和寫入主進程任何數(shù)據(jù)。需要建立一套 rpc 通信協(xié)議打通插件和主進程的調(diào)用。
2.2.1主進程接口安全暴露
excel 通過 di 依賴服務(wù)化后,各種依賴將會以服務(wù)化的形式對外提供。對外暴露 api 接口,提供給內(nèi)部和外部調(diào)用。
2.2.2插件接口安全暴露
基于安全性考慮,插件只能調(diào)用平臺方提供的安全接口,這些接口可以 api 服務(wù)化的形式對外暴露。在初始化的過程注入到一個 API 服務(wù)工廠中返回給一個緩存對象,提供給插件使用。這些對象如何暴露給插件?這里我們參考 vscode 機制,可以攔截 require 接口,將緩存的插件api 注入到插件上下文。
2.2.3 插件進程 api 和 主進程 api 通信
定義標準的 worker/iframe 進程與主進程通信機制。參照 vscode 我們可以巧用 proxy 代理(IE 11 不兼容),在插件調(diào)用 api 時進行攔截,統(tǒng)一轉(zhuǎn)換成 message send 調(diào)用,可以避免每次api 調(diào)用手動觸發(fā) message 通信,簡化調(diào)用流程。
2.3 插件 UI 擴展點
騰訊文檔公共組件交互上只有兩種組成,分別是 dialog 彈窗和 slidebar 側(cè)邊欄,dialog 彈窗代表是添加文件夾面包、分享面板、vip 支付面板等。側(cè)邊欄有權(quán)限、通知列表等。這兩種類型組件,我們分別為插件 UI 展示提供統(tǒng)一的面板。插件編寫時需要配置指定類型,調(diào)用時在特定區(qū)域承載視圖。
2.4 插件管理體系
2.4.1 部署
用戶開發(fā)的插件需要有管理平臺,按照規(guī)范開發(fā)完后,發(fā)布到插件管理服務(wù)。管理服務(wù)具備生成插件描述信息,部署到靜態(tài)資源,為 UI 組件形態(tài)的插件動態(tài)生成插件三級域名。
2.4.鑒權(quán)、安裝
用戶授權(quán)給插件,然后才能完成安裝。可訪問權(quán)限比如用戶基本信息,表格信息,確認許可后,用戶信息下綁定應(yīng)對插件。
2.5 調(diào)試
內(nèi)部插件暫時可以直接代理 sheet 本地進行開發(fā)。對外部插件需要提供一種標準便捷的調(diào)試方式。可選方案有兩種,第一種是通過騰訊文檔調(diào)試工具 Chrome 插件,支持用戶安裝臨時的本地插件,進行開發(fā)。
另外一種是用戶申請調(diào)試開發(fā)權(quán)限,文檔菜單選項內(nèi)增加插件導(dǎo)入,然后上傳到一個臨時的調(diào)試服務(wù)服務(wù) ,調(diào)試好后,再進行發(fā)布。?
2.5 如何兼容多品類
公用組件插件化依賴品類有相同的服務(wù)化機制。但各個品類因為代碼并不統(tǒng)一,插件化如何兼容各個品類呢?
有兩種主要方法,第一種是公共組件按照 Excel 服務(wù)化進行插件化先行改造,內(nèi)部再暴露全局變量給其他未改造的品類按照原 FC 調(diào)用。
另外一種是將插件化體系進行單獨的 SDK 化,SDK 內(nèi)部做統(tǒng)一的插件化環(huán)境及初始化流程,在各個品類再進行引入。
結(jié)
任何架構(gòu)設(shè)計都是歷史下的產(chǎn)物,脫離實際情況談最優(yōu)解都是不切實際的想法,如何在有限的人力資源和更優(yōu)的方案中取得平衡是一門學(xué)問。一個模式的提出必定面對解決一個問題,隨著時間的推移,需求不斷調(diào)整和迭代之下,原先的軟件設(shè)計必定會變得越來越脆弱,最終面臨自然崩塌,需要重構(gòu)。但就像一棟房子,工程師設(shè)計出結(jié)構(gòu)穩(wěn)定和考慮長遠的方案(架構(gòu)和可擴展性),同時施工隊不偷工減料(代碼質(zhì)量),那么房子也會保值更久,也能更好的面對新工程的不斷改造。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ----------? END? ----------
推薦閱讀
??2020最新java學(xué)習(xí)路線圖!
??Java ?9 月程序員工資出爐,你拖后腿了嗎?
??CTO 寫的代碼為什么那么強!
?一個900行代碼得類,你累不累?
如有收獲,點個在看,誠摯感謝下次見(。・ω・。)ノ?總結(jié)
以上是生活随笔為你收集整理的导入第三方组件_大型 web 应用公共组件架构是如何来的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安装python的moviepy_Mov
- 下一篇: 箱式图 分组_小白学R(三):重复测量数