美团点评境外度假团队前端项目开发实践总结
隨著前端項(xiàng)目數(shù)量和規(guī)模越來(lái)越大,參與的人員也越來(lái)越多,如何在前端項(xiàng)目開(kāi)發(fā)過(guò)程中保證優(yōu)質(zhì)的開(kāi)發(fā)者體驗(yàn)和項(xiàng)目的可維護(hù)性,同時(shí)確保極致的用戶體驗(yàn)將會(huì)是一個(gè)非常大的挑戰(zhàn)。
為了應(yīng)對(duì)這個(gè)挑戰(zhàn),美團(tuán)點(diǎn)評(píng)境外度假前端研發(fā)團(tuán)隊(duì)自2016年6月起啟動(dòng)了面向C端用戶的”赫爾墨斯”項(xiàng)目,主要圍繞以下幾個(gè)方面進(jìn)行展開(kāi):
- 前后端分離:前端擁有完整獨(dú)立的開(kāi)發(fā)、測(cè)試、部署的流程,與后端完全分離,減少溝通成本。
- 模塊化與組件化:封裝可重用UI組件、業(yè)務(wù)邏輯,提升代碼庫(kù)的可復(fù)用性、可測(cè)試性。
- 流程自動(dòng)化:提升效率、避免重復(fù)手工工作、保證質(zhì)量、自動(dòng)資源優(yōu)化等等。
- 頁(yè)面加載性能優(yōu)化:建立前端監(jiān)控體系、優(yōu)化資源加載、使用離線化策略。
在之前的項(xiàng)目中,頁(yè)面是由Java后端項(xiàng)目中通過(guò)FTL模板引擎拼裝,前端團(tuán)隊(duì)會(huì)維護(hù)另外一個(gè)前端的項(xiàng)目,存放相應(yīng)的CSS和JS文件,最后通過(guò)公司內(nèi)部的Cortex系統(tǒng)打包發(fā)布。
這個(gè)流程的問(wèn)題在于前端對(duì)于整個(gè)頁(yè)面入口沒(méi)有控制力,需要依賴后端的FTL拼裝,頁(yè)面的內(nèi)容需要更改時(shí),前后端同學(xué)就要反復(fù)溝通協(xié)調(diào),整體效率比較差,容易出錯(cuò),也不方便實(shí)現(xiàn)前端相關(guān)的優(yōu)化。更坑的是有時(shí)候還要求前端同學(xué)安裝一整套后端的開(kāi)發(fā)環(huán)境,費(fèi)時(shí)費(fèi)力不說(shuō),光維護(hù)這套不斷變換的環(huán)境就要費(fèi)不少精力。
因此,我們認(rèn)為前后端分離的關(guān)鍵點(diǎn)在于前端擁有完整獨(dú)立的開(kāi)發(fā)、測(cè)試、部署的流程,與后端完全分離。
在赫爾墨斯項(xiàng)目中,我們把頁(yè)面的組裝完全放置到了前端項(xiàng)目,后端只提供AJAX的接口用于獲取和提交數(shù)據(jù)。前端頁(yè)面完全靜態(tài)化,構(gòu)建完畢之后連同相應(yīng)的靜態(tài)資源通過(guò)CI直接發(fā)布到CDN。
模塊化開(kāi)發(fā)在其它開(kāi)發(fā)領(lǐng)域(比如客戶端后端開(kāi)發(fā))已經(jīng)實(shí)施了很多年了,而在前端開(kāi)發(fā)領(lǐng)域,一直沒(méi)有一個(gè)統(tǒng)一的模塊化的規(guī)范。隨著ES6 Module規(guī)范的落地,這個(gè)問(wèn)題終于(部分)解決了。模塊化開(kāi)發(fā)的優(yōu)勢(shì)主要有以下幾個(gè)方面。
- 更好的代碼組織結(jié)構(gòu)和開(kāi)發(fā)協(xié)作:通過(guò)細(xì)致的文件夾、文件拆分,更易于管理復(fù)雜的代碼庫(kù),更易于多人協(xié)作開(kāi)發(fā),降低文件合并時(shí)候沖突的發(fā)生概率,方便編寫單元測(cè)試。
- 依賴管理:不再需要手動(dòng)管理腳本的加載順序。
- 優(yōu)化:
- 代碼打包(Bundle):合并小模塊,抽取公共模塊,在資源請(qǐng)求數(shù)和瀏覽器緩存利用方面進(jìn)行合適的取舍。
- 代碼分割(Split):允許按需加載JS代碼(分路由、異步組件),解決單頁(yè)面應(yīng)用(SPA)首屏加載速度問(wèn)題。
- Tree Shaking:利用ES6模塊的靜態(tài)化特性,可以在構(gòu)建過(guò)程中分析出代碼庫(kù)中未使用到的代碼,從最終的bundle中去除,從而減少JS Bundle的尺寸。
- Scope Hoisting:ES6模塊內(nèi)容導(dǎo)入和導(dǎo)出綁定是活動(dòng)的,可以將多個(gè)小模塊合并到一個(gè)函數(shù)當(dāng)中,對(duì)于重復(fù)變量名進(jìn)行合適的重命名,從而減少Bundle的尺寸和提升加載速度。
如果說(shuō)模塊化是解決如何封裝和復(fù)用一段邏輯代碼的話,組件化要解決的是如何封裝和復(fù)用一個(gè)用戶界面元素,例如,一個(gè)按鈕、一個(gè)彈出框,亦或是一個(gè)輪播圖。由于瀏覽器原生并沒(méi)有提供這么一套組件化開(kāi)發(fā)的API,這個(gè)領(lǐng)域目前也是處在相對(duì)不穩(wěn)定的狀態(tài)中,各種框架層出不窮,比較有代表性的有React、Vue和Angular。我們最終選擇的是Vue.js作為我們組件化開(kāi)發(fā)的基礎(chǔ)API(W3C實(shí)際上有一套Web Component的規(guī)范,目前已定稿,但是瀏覽器支持非常有限。同時(shí)功能上缺乏了現(xiàn)在框架普遍擁有的數(shù)據(jù)綁定、同構(gòu)渲染等等)。
主要是基于以下幾個(gè)方面的考慮。
- 體積:19kB(min+gzip)
- API和學(xué)習(xí)成本:
- 聲明式組件模板和分離樣式表,更接近于傳統(tǒng)開(kāi)發(fā)模式,抵觸心理小。
- 響應(yīng)式的組件狀態(tài)跟蹤:更新?tīng)顟B(tài)代碼更簡(jiǎn)潔,組件樹(shù)重新渲染效率更高。
- 清晰簡(jiǎn)潔的生命周期鉤子函數(shù)和單向數(shù)據(jù)流:頁(yè)面邏輯和狀態(tài)更新更可控。
- 運(yùn)行時(shí)報(bào)錯(cuò)和告警詳細(xì):方便新手入門和規(guī)避常見(jiàn)錯(cuò)誤。
- 工具鏈完整性:webpack Loader(加載Vue單文件組件)、開(kāi)發(fā)者工具(Dev Tools)、腳手架(vue-cli)、單元測(cè)試友好(vue-test-utils)。
- 運(yùn)行時(shí)性能:
- Virtual DOM來(lái)管理組件樹(shù)渲染到真實(shí)DOM的狀態(tài)同步,使用高效的算法來(lái)最小化DOM操作的次數(shù)。
- 由于響應(yīng)式設(shè)計(jì),不需要優(yōu)化組件樹(shù)再次渲染的范圍。
- 組件樹(shù)靜態(tài)部分被單獨(dú)處理,重新渲染不需要重新構(gòu)建。
- 同構(gòu)渲染:
- 高性能、開(kāi)箱即用的方案,包括前后端可用的路由和狀態(tài)管理組件,降低了使用的門檻。
- 深度webpack集成,簡(jiǎn)化了代碼分割和構(gòu)建調(diào)試流程。
Vue.js提供了一種單文件組件的格式允許把一個(gè)組件相關(guān)聯(lián)的模板、邏輯和樣式寫在一個(gè)文件當(dāng)中,通過(guò)上文提到的一個(gè)定制化的webpack loader可以把它轉(zhuǎn)換為一個(gè)包含Vue.js的組件配置對(duì)象的模塊被其它模塊引用。
基于Vue.js,我們開(kāi)發(fā)了一套適合移動(dòng)端開(kāi)發(fā)的組件庫(kù)dora-ui,提供了一套符合我們團(tuán)隊(duì)業(yè)務(wù)需求的基礎(chǔ)組件庫(kù),它主要由以下幾個(gè)部分構(gòu)成
- 20個(gè)Vue.js 2.0兼容組件,涵蓋布局、導(dǎo)航、數(shù)據(jù)輸入、數(shù)據(jù)展示、信息反饋等等方面。
- 組件文檔:每一個(gè)組件需要有一個(gè)相應(yīng)的Readme(markdown格式)文件,描述組件的用途、屬性、事件、插槽等等。
- 組件示例:每一個(gè)組件可以有一個(gè)或者多個(gè)示例,來(lái)展示組件的用法。
- 組件復(fù)用度查詢:可以快速查找一個(gè)組件被多少個(gè)頁(yè)面所引用以及一個(gè)頁(yè)面引用了多少個(gè)組件。
- webpack plugin:在項(xiàng)目構(gòu)建時(shí)候收集項(xiàng)目頁(yè)面和組件引用關(guān)系,輸出一個(gè)JSON文件。
- 查詢頁(yè)面:通過(guò)讀取上述JSON文件,提供一個(gè)界面供開(kāi)發(fā)人員查詢。
在工程標(biāo)準(zhǔn)化自動(dòng)化方面,我們想要達(dá)到的目標(biāo)是統(tǒng)一技術(shù)棧,保持技術(shù)棧的先進(jìn)性,規(guī)范化代碼樣式以及自動(dòng)化一切可以自動(dòng)化的任務(wù)。
所有可以自動(dòng)化的任務(wù)都應(yīng)該被自動(dòng)完成。
工程模板
我們建立了統(tǒng)一的項(xiàng)目模板,基于約定大于配置的理念,簡(jiǎn)化了新項(xiàng)目創(chuàng)建的流程以及頁(yè)面和組件的開(kāi)發(fā)和調(diào)試。
本地組件測(cè)試開(kāi)發(fā)
為了方便開(kāi)發(fā)和測(cè)試單個(gè)組件,我們?cè)诿總€(gè)組件的目錄下面會(huì)創(chuàng)建一個(gè)demo目錄。在構(gòu)建過(guò)程中,借助webpack的require.context API來(lái)獲取components目錄下所有組件的demo文件,隨后為每個(gè)組件Demo創(chuàng)建一個(gè)路由。
var demoRequire = require.context('@component', true, /demo\/.*\.vue$/); //遍歷取出所有demo組件 const demos = demoRequire.keys().map(demoKey => {var [componentName, demoName] = demoKey.split('/demo/');componentName = componentName.substring(2);demoName = demoName.substring(0, demoName.lastIndexOf('.'));return {componentName: componentName,demoName: demoName,component: demoRequire(demoKey)} });//組成key + value 形式的demo組件對(duì)象集合 const demosByComponent = _.groupBy(demos, demo => {return demo.componentName; });//整個(gè)組件頁(yè)面的路由 const routesByComponent = Object.keys(demosByComponent).map(componentName => {return {path: '/' + componentName,component: require('./component.vue'),meta: {componentName: componentName,demoComponents: demosByComponent[componentName]}} }); //組件頁(yè)面內(nèi)調(diào)試每個(gè)單獨(dú)demo的路由 const routesByDemo = demos.map(demo => {return {path: '/' + demo.componentName + '/' + demo.demoName,component: demo.component,meta: {componentName: demo.componentName,}} });本地Mock服務(wù)
前后端分離之后,為了加速前后端并行開(kāi)發(fā)的效率,我們基于webpack-dev-server,實(shí)現(xiàn)了一套本地Mock服務(wù),能夠在本地開(kāi)發(fā)環(huán)境模擬任意API請(qǐng)求的響應(yīng)。
同時(shí)為了提升效率,根據(jù)模板工程目錄的約定,這些Mock文件能夠被自動(dòng)發(fā)現(xiàn)同時(shí)一旦發(fā)生變更可以實(shí)時(shí)刷新。
關(guān)于頁(yè)面加載性能優(yōu)化,我們首先要建立監(jiān)控體系,收集用戶側(cè)真實(shí)數(shù)據(jù),然后基于數(shù)據(jù)進(jìn)行頁(yè)面加載的優(yōu)化。
同時(shí),為了進(jìn)一步提升用戶體驗(yàn),我們還進(jìn)行了前端離線化的支持。
監(jiān)控體系
建立一個(gè)完整的監(jiān)控體系是性能優(yōu)化的前提條件。我們認(rèn)為,前端監(jiān)控體系大體由3部分構(gòu)成(下圖)。
技術(shù)監(jiān)控服務(wù)于開(kāi)發(fā)人員,收集開(kāi)發(fā)人員所需要的性能及異常相關(guān)的數(shù)據(jù)。
用戶行為監(jiān)控服務(wù)于產(chǎn)品和運(yùn)營(yíng),主要收集用戶在頁(yè)面上操作的行為,比如點(diǎn)擊、曝光等等。
展示查詢提供可視化查詢工具,通過(guò)報(bào)表、圖表、儀表盤的形式,滿足對(duì)于數(shù)據(jù)可視化的需求。
網(wǎng)絡(luò)鏈路優(yōu)化
對(duì)于靜態(tài)資源,從海外回源的成本非常高,通過(guò)對(duì)接海外的CDN供應(yīng)商,能夠在世界各地部署多個(gè)靜態(tài)資源的緩存代理,根據(jù)用戶的地理位置選擇最近的位置進(jìn)行靜態(tài)資源的分發(fā)。
同時(shí)通過(guò)增加香港中間源,及中間源到源站的專線減少?gòu)暮M庵苯踊卦丛凑驹斐傻男阅荛_(kāi)銷。
對(duì)于AJAX請(qǐng)求,在香港部署了SLB來(lái)做中轉(zhuǎn),SLB與后臺(tái)服務(wù)是通過(guò)專線連接的。
主文檔回源優(yōu)化
由于主文檔無(wú)法進(jìn)行長(zhǎng)緩存,針對(duì)主文檔回源過(guò)于頻繁的問(wèn)題,我們通過(guò)在CDN邊緣節(jié)點(diǎn)覆蓋源站緩存設(shè)置,將主文檔緩存30天,使得主文檔回源減少(注意:用戶側(cè)看到的仍然是源站設(shè)置的緩存時(shí)間,用戶側(cè)設(shè)置為10分鐘)。
同時(shí),通過(guò)在發(fā)布流程當(dāng)中加入主動(dòng)清除海外CDN緩存的功能,來(lái)解決緩存更新的問(wèn)題。
域名收斂 & 減少請(qǐng)求數(shù)
存在問(wèn)題:
- 頁(yè)面引用的第三方腳本,比如監(jiān)控、打點(diǎn),缺乏海外CDN及長(zhǎng)緩存支持,這些腳本的存在影響了加載時(shí)間。
- 多個(gè)域名也增加了域名解析的成本和建立連接的成本。
我們的做法是把第三方腳本打包到我們的代碼里面,并抽取公共代碼已增加緩存的效率,同時(shí)把所有靜態(tài)資源和主文檔公用一個(gè)域名。
離線化
由于境外行中場(chǎng)景網(wǎng)絡(luò)不穩(wěn)當(dāng),無(wú)法保持實(shí)時(shí)在線,我們有些工具類的頁(yè)面比方說(shuō)匯率助手等等實(shí)際上在離線情況下也能夠使用。此外,離線化也能提升加載速度,因?yàn)橹魑臋n也不再需要網(wǎng)絡(luò)請(qǐng)求了。
考慮到瀏覽器多平臺(tái)兼容性問(wèn)題,我們最終是基于HTML Application Cache API來(lái)打造了我們的離線化方案(下圖)。
在構(gòu)建流程中,通過(guò)分析頁(yè)面資源依賴關(guān)系,自動(dòng)生成資源manifest文件,這樣就能夠確保頁(yè)面及資源發(fā)生變更時(shí),manifest文件內(nèi)容同步更新。
需要特別注意的是,當(dāng)用戶再次訪問(wèn)訪問(wèn)頁(yè)面的時(shí)候,如果頁(yè)面的manifest發(fā)生變更,瀏覽器會(huì)自動(dòng)重新下載manifest里面的文件,完成之后會(huì)在applicationCache對(duì)象上發(fā)出updateready事件,但是并不會(huì)自動(dòng)刷新頁(yè)面,也就是說(shuō)這個(gè)時(shí)候用戶會(huì)看到之前的版本,而不是最新的版本。當(dāng)用戶再次進(jìn)入這個(gè)頁(yè)面的時(shí)候,將會(huì)訪問(wèn)到最新的版本。在大部分情況下,這都不是問(wèn)題,因?yàn)橐苿?dòng)端網(wǎng)頁(yè)的停留時(shí)間是非常有限的。假設(shè)某一次頁(yè)面更新非常重要,期待用戶立即就進(jìn)行頁(yè)面刷新,我們可以在監(jiān)聽(tīng)到updateready事件之后,給用戶一個(gè)友好的提示,讓他主動(dòng)刷新頁(yè)面(如上圖左下所示)。
后續(xù)規(guī)劃
現(xiàn)在使用的靜態(tài)頁(yè)+前端渲染的策略,針對(duì)初次訪問(wèn)的用戶在首屏?xí)r間上仍然有可改善的空間。后續(xù)我們會(huì)采用基于Vue的同構(gòu)渲染+代碼分割對(duì)于這一問(wèn)題進(jìn)行進(jìn)一步優(yōu)化。
對(duì)于離線化方案,AppCache未來(lái)會(huì)逐步被Service Worker所取代,無(wú)論從靈活性還是可擴(kuò)展性而言,SW都更勝一籌。后續(xù)我們會(huì)逐步過(guò)渡到基于SW的方案,實(shí)現(xiàn)一個(gè)更加透明的網(wǎng)絡(luò)層代理,能同時(shí)處理靜態(tài)資源和動(dòng)態(tài)請(qǐng)求。
Web平臺(tái)正在以飛快地速度向前發(fā)展,比如WebGL、WebVR、HTTP/2、Service Worker、Web Assembly、WebRTC這些激動(dòng)人心的功能逐漸在各大瀏覽器中落地,前端開(kāi)發(fā)人員能夠?qū)懗龈旄犰诺挠脩艚缑?#xff0c;用戶能夠得到更優(yōu)質(zhì)的Web體驗(yàn)。
在赫爾墨斯項(xiàng)目中,我們實(shí)施了前后端分離、模塊化和組件化改造、流程自動(dòng)化、接入了監(jiān)控和報(bào)表系統(tǒng),極大的提高了我們的開(kāi)發(fā)效率和項(xiàng)目代碼的可維護(hù)、可復(fù)用性,同時(shí)通過(guò)自動(dòng)化的資源優(yōu)化,確保了有效的優(yōu)化策略被以極低的成本在多個(gè)項(xiàng)目中復(fù)用。
毓杰,美團(tuán)點(diǎn)評(píng)前端技術(shù)專家,全棧開(kāi)發(fā)工程師。2016年加入美團(tuán)點(diǎn)評(píng),負(fù)責(zé)境外度假前端研發(fā)組的工作。崇尚自由、開(kāi)放、互通的技術(shù)平臺(tái),追求極致的用戶體驗(yàn)和開(kāi)發(fā)效率。
總結(jié)
以上是生活随笔為你收集整理的美团点评境外度假团队前端项目开发实践总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: iOS 覆盖率检测原理与增量代码测试覆盖
- 下一篇: 前端感官性能的衡量和优化实践