前端“智能”靜態資源管理
模塊化/組件化開發,僅僅描述了一種開發理念,也可以認為是一種開發規范,倘若你認可這規范,對它的分治策略產生了共鳴,那我們就可以繼續聊聊它的具體實現了。
很明顯,模塊化/組件化開發之后,我們最終要解決的,就是模塊/組件加載的技術問題。然而前端與客戶端GUI軟件有一個很大的不同:
前端是一種遠程部署,運行時增量下載的GUI軟件
前端應用沒有安裝過程,其所需程序資源都部署在遠程服務器,用戶使用瀏覽器訪問不同的頁面來加載不同的資源,隨著頁面訪問的增加,漸進式的將整個程序下載到本地運行,“增量下載”是前端在工程上有別于客戶端GUI軟件的根本原因。
上圖展示了一款界面繁多功能豐富的應用,如果采用Web實現,相信也是不小的體量,如果用戶第一次訪問頁面就強制其加載全站靜態資源再展示,相信會有很多用戶因為失去耐心而流失。根據“增量”的原則,我們應該精心規劃每個頁面的資源加載策略,使得用戶無論訪問哪個頁面都能按需加載頁面所需資源,沒訪問過的無需加載,訪問過的可以緩存復用,最終帶來流暢的應用體驗。
這正是Web應用“免安裝”的魅力所在。
由“增量”原則引申出的前端優化技巧幾乎成為了性能優化的核心,有加載相關的按需加載、延遲加載、預加載、請求合并等策略;有緩存相關的瀏覽器緩存利用,緩存更新、緩存共享、非覆蓋式發布等方案;還有復雜的BigRender、BigPipe、Quickling、PageCache等技術。這些優化方案無不圍繞著如何將增量原則做到極致而展開。
所以我覺得:
前端開發最迫切需要做好的就是在基礎架構中貫徹增量原則。
相信這種貫徹不會隨著時間的推移而改變,在可預見的未來,無論在HTTP1.x還是HTTP2.0時代,無論在ES5亦或者ES6/7時代,無論是AMD/CommonJS/UMD亦或者ES6 module時代,無論端內技術如何變遷,我們都有足夠充分的理由要做好前端程序資源的增量加載。
我覺得是在其基礎架構中缺少這樣一種“智能”的資源加載方案。沒有這樣的方案,很難將前端應用的規模發展到第四階段,很難實現落地前面介紹的那種組件化開發方案,也很難讓多方合作高效率的完成一項大型應用的開發,并保證其最終運行性能良好。在第四階段,我們需要強大的工程化手段來管理”玩具般簡單“的前端開發。
在我的印象中,Facebook是這方面探索的偉大先驅之一,早在2010年的Velocity China大會上,來自Facebook的David Wei博士就為業界展示了他們令人驚艷的靜態網頁資源管理和優化技術。
David Wei博士在當年的交流會上提到過一些關于Facebook的一些產品數據:
Facebook整站有10000+個靜態資源;
每個靜態資源都有可能被翻譯成超過100種語言版本;
每種資源又會針對瀏覽器生成3種不同的版本;
要針對不同帶寬的用戶做5種不同的打包方法;
有3、4個不同的用戶組,用于小批次體驗新的產品功能;
還要考慮不同的送達方法,可以直接送達,或者通過iframe的方式提升資源并行加載的速度;
靜態資源的壓縮和非壓縮狀態可切換,用于調試和定位線上問題
這是一個狀態爆炸的問題,將所有狀態乘起來,整個網站的資源組合方式會達到幾百萬種之多(去重之后統計大概有300萬種組合方式)。支撐這么大規模前端項目運行的底層架構正是魏博士在那次演講中分享的Static Resource Management System(靜態資源管理系統),用以解決Facebook項目中有關前端工程的3D問題(Development,Deployment,Debugging)。
那段時間?FIS?項目正好遇到瓶頸,當時的FIS還是一個用php寫的task-based構建工具,那時候對于前端工程的認知度很低,覺得前端構建不就是幾個壓縮優化校驗打包任務的組合嗎,寫好流程調度,就針對不同需求寫插件唄,看似非常簡單。但當我們支撐越來越多的業務團隊,接觸到各種不同的業務場景時,我們深刻的感受到task-based工具的粗糙,團隊每天疲于根據各種業務場景編寫各種打包插件,構建邏輯異常復雜,隱隱看到不可控的跡象。
我們很快意識到把基礎架構放到構建工具中實現是一件很愚蠢的事,試圖依靠構建工具實現各種優化策略使得構建變成了一個巨大的黑盒,一旦發生問題,定位起來非常困難,而且每種業務場景都有不同的優化需求,構建工具只能通過靜態分析來優化加載,具有很大的局限性,單頁面/多頁面/PC端/移動端/前端渲染/后端渲染/多語言/多皮膚/高級優化等等資源加載問題,總不能給每個都寫一套工具吧,更何況這些問題彼此之間還可以有多種組合應用,工具根本寫不過來。
Facebook的做法無疑為我們亮起了一盞明燈,不過可惜它并不開源(不是技術封鎖,而是這個系統依賴FB體系中的其他方面,通用性不強,開源意義不大),我們只能嘗試挖掘相關信息,網上對它的完整介紹還是非常非常少,分析facebook的前端代碼也沒有太多收獲,后來無意中發現了facebook使用的項目管理工具phabricator中的一個靜態管理方案Celerity,以及相關的說明,看它的描述很像是Facebook靜態資源管理系統的一個mini版!
簡單看過整個系統之后發現原理并不復雜(小而美的典范),它是通過一個小工具掃描所有靜態資源,生成一張資源表,然后有一個PHP實現的資源管理框架(Celerity)提供了資源加載接口,替代了傳統的script/link等靜態的資源加載標簽,最終通過查表來加載資源。
雖然沒有真正看過FB的那套系統,但眼前的這個小小的框架給了當時的我們足夠多的啟示:
靜態資源管理系統 = 資源表 + 資源加載框架
多么優雅的實現啊!
資源表是一份數據文件(比如JSON),是項目中所有靜態資源(主要是JS和CSS)的構建信息記錄,通過構建工具掃描項目源碼生成,是一種k-v結構的數據,以每個資源的id為key,記錄了資源的類別、部署路徑、依賴關系、打包合并等內容,比如:
{
"a.js": {
"url": "/static/js/a.5f100fa.js",
"dep": [ "b.js", "a.css" ]
},
"a.css": {
"url": "/static/css/a.63cf374.css",
"dep": [ "button.css" ]
},
"b.js": {
"url": "/static/js/b.97193bf.js"
},
"button.css": {
"url": "/static/css/button.de33108.js"
}
}
而資源加載框架則提供一些資源引用的API,讓開發者根據id來引用資源,替代靜態的script/link標簽來收集、去重、按需加載資源。調用這些接口時,框架通過查表來查找資源的各項信息,并遞歸查找其依賴的資源的信息,然后我們可以在這個過程中實現各種性能優化算法來“智能”加載資源。
根據業務場景的不同,加載框架可以在瀏覽器中用JS實現,也可以是后端模板引擎中用服務端語言實現,甚至二者的組合,不一而足。
這種設計很快被驗證具有足夠的靈活性,能夠完美支撐不同團隊不同技術規范下的性能優化需求,前面提到的按需加載、延遲加載、預加載、請求合并、文件指紋、CDN部署、Bigpipe、Quickling、BigRender、首屏CSS內嵌、HTTP 2.0服務端推送等等性能優化手段都可以很容易的在這種架構上實現,甚至可以根據性能日志自動進行優化(Facebook已實現)。
因為有了資源表,我們可以很方便的控制資源加載,通過各種手段在運行時計算頁面的資源使用情況,從而獲得最佳加載性能。無論是前端渲染的單頁面應用,還是后端渲染的多頁面應用,這種方法都同樣適用。
此外,它還很巧妙的約束了構建工具的職責——只生成資源表。資源表是非常通用的數據結構,無論什么業務場景,其業務代碼最終都可以被掃描為相同結構的表數據,并標記資源間的依賴關系,有了表之后我們只需根據不同的業務場景定制不同的資源加載框架就行了,從此徹底告別一個團隊維護一套工具的時代!!!
恩,如你所見,雖然徹底告別了一個團隊一套工具的時代,但似乎又進入了一個團隊一套框架的時代。其實還是有差別的,因為框架具有很大的靈活性,而且不那么黑盒,采用框架實現資源管理相比構建更容易調試、定位和升級變更。
深耕靜態資源加載框架可以帶來許多收益,而且有足夠的靈活性和健壯性面向未來的技術變革,這個我們留作后話。
本文來自http://github.com/fouber/blog,著作權屬于原作者@前端農名工。