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