1. 程式人生 > 其它 >美團點評境外度假團隊前端專案開發實踐總結

美團點評境外度假團隊前端專案開發實踐總結

前言

隨著前端專案數量和規模越來越大,參與的人員也越來越多,如何在前端專案開發過程中保證優質的開發者體驗和專案的可維護性,同時確保極致的使用者體驗將會是一個非常大的挑戰。

為了應對這個挑戰,美團點評境外度假前端研發團隊自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體驗。

在赫爾墨斯專案中,我們實施了前後端分離、模組化和元件化改造、流程自動化、接入了監控和報表系統,極大的提高了我們的開發效率和專案程式碼的可維護、可複用性,同時通過自動化的資源優化,確保了有效的優化策略被以極低的成本在多個專案中複用。