愛彼迎將JavaScript代碼打包工具從Webpack改用Metro,縮短了構建時間
譯文?譯者 | 布加迪
審校 | 孫淑娟
與許多大規模公司一樣,隨著代碼庫不斷變大,愛彼迎也在打包工具方面經歷了陣痛。即使代碼庫增至四倍,愛彼迎在2018年將JavaScript代碼打包工具從Webpack遷移到Metro后,還是加快了對前端的UX更改。
構建性能顯著提升后,從交互時間(TTI)這個指標來看,UI更改速度加快了80%。即使是最慢的生產級構建,編譯49000個模塊(JavaScript文件)現在也快了55%,從使用Webpack時的30.5分鐘縮短至13.8分鐘。
至于使用Metro構建的那些頁面,愛彼迎自己的頁面性能得分(PPS)也有所提高(提高約1%)。
圖1
作為參考,在2018年左右代碼庫增至四倍之后,簡單一行代碼更改的平均頁面刷新時間在30秒到2分鐘之間,具體取決于項目大小。
愛彼迎軟件工程師Rae Liu在最近的這篇??博文??(中介紹了Webpack和Metro的一些差異,并討論了一些遷移挑戰。
一、Metro簡介
Metro由Meta開發,是面向React Native的開源JavaScript代碼打包工具。本文介紹Metro的自定義版本,因為愛彼迎的架構不包含React Native。降了與本公司的團隊合作外,愛彼迎的工程師還直接與Meta的Metro工程師合作,進一步開發這項技術。
Metro按以下順序將打包分為三個步驟:解析、轉換和序列化。
- 解析:解析import/require語句。
- 轉換:轉譯代碼(源到源編譯器將現代Typescript/JavaScript源代碼轉換成JavaScript,并與舊瀏覽器向后兼容),典型的工具是babel。
- 序列化:將轉換后的文件組合成包。
在開發過程中,愛彼迎工程師創建了一個帶自定義端點的Metro服務器系統,以處理構建依賴圖和源映射、轉換以及捆綁JS和CSS文件。至于生產級構建,他們將Metro作為Node API來運行,以處理解析、轉換和序列化。
遷移分兩個階段進行。最要緊的是Metro開發服務器,因為緩慢的Webpack開發服務器是導致開發生產力成本高昂的根源。第二個遷移階段致力于讓Metro在功能上與Webpack相當,并在生產環境下在Metro和Webpack之間運行A/B測試。
二、Metro和Webpack的兩大區別
按需處理JavaScript包
Webpack在啟動時預編譯整個項目,而Metro只編譯需要的內容。也就是說,JavaScript包嚴格上來說就是序列化的依賴圖,其中入口點是這張圖的根(root)。
在愛彼迎,每個前端項目都有一個Node服務器來匹配通向特定入口點的路由。請求網頁時,DOM包含帶有開發JavaScript URL的腳本標簽。Webpack需要知道所有頁面的所有入口點,之后才能開始打包,而Metro只需要一個入口點,就可以根據請求處理JavaScript包。
開發人員對頁面A進行了更改,但下圖中看不到:
圖2
在上圖的1a和1b中,瀏覽器加載頁面A(1),從打包工具請求entryPageA.js文件(2),打包工具使用適當的包響應瀏覽器(4)。圖1a和圖1b的區別在于操作(3),因為 Webpack圖編譯了頁面B和C的入口點,而Metro并非如此,因為開發人員在示例中只修改了頁面A。
愛彼迎最大的前端項目之一有26000個獨特的模塊,每頁的模塊中位數約7.2個模塊。由于使用了服務器端渲染,愛彼迎最終要處理的模塊數量翻番,達到大約48000個。在將Metro的按需編譯模型付諸實施之后,現在減少了大約70%的工作量。
多層緩存
圖2
愛彼迎利用Metro的多層緩存功能以及持久性和非持久性緩存。Metro允許工程師定義緩存實現,包括混合不同類型的緩存層,這樣提供了更高的緩存靈活性。
愛彼迎按優先級對緩存層排序。如果在一個緩存層中沒有找到結果,將使用下一層,直至找到結果。與沒有緩存的默認Metro實現相比,在一個編譯22000個文件的項目中,命中遠程只讀緩存可使服務器構建速度提高56%。
第三個緩存層是遠程只讀緩存而不是讀寫緩存,因為寫入到遠程緩存會帶來昂貴的網絡調用,尤其是在慢速網絡上。這個決定在開發中另外節省了17%的構建時間。
Webpack有一個緩存層,不過它與Metro提供的緩存層不一樣。
三、包拆分
愛彼迎的博文中詳述的技術挑戰之一是包拆分(Bundle Splitting)。這是通過動態導入邊界拆分包的過程(又叫代碼拆分)。開箱即用的Metro解決方案為每個入口點生成約5MiB 的巨大包,這對瀏覽器資源和網絡延遲造成了負擔,無法進行HTTP緩存。
在上圖中,import(‘./file’) 表示動態導入邊界。左側的包(3a)被拆分成右側的三個小包(3b)。執行import('./file') 語句時請求額外的包。
假設fileA.js發生了更改,需要重新下載整個包,以便瀏覽器獲取fileA.js中的更改。如圖3b所示,由于包由動態導入拆分,fileA.js中的更改只導致重新下載fileA.js包。其余的包可以重用瀏覽器緩存內容。
在生產環境下,沒有開發服務器,包是預先構建的。愛彼迎工程師從Webpack的包拆分算法中獲得了靈感,實現了一種類似的機制來拆分Metro依賴圖。與動態導入邊界的開發拆分相比,愛彼迎上生成的包大小減少了約20%(由1549 KB變成1226 KB)。
開發包的優化方式不一樣,因為運行包拆分算法需要時間,工程師們不想在開發中浪費時間來拆分包大小。在開發情形下,頁面加載性能的優先級高于包大小實現最小化。
Metro和Webpack在包大小方面的指標具有可比性。
標題:??Airbnb Moves from Webpack to Metro, Enjoys Shorter Build Times???,作者:Jessica Wachtel?