概述
2019年下半年,為了將微信錢包/支付寶九宮格入口的滴滴出行遷移為小程式,團隊對小程式進行了大量的功能升級與補全。在整個過程中也遇到並克服了一系列問題和挑戰,其中包體積問題尤為突出。接下來全面介紹一下滴滴出行小程式在體積控制方面做的努力與沉澱。
背景
微信對小程式包體積的要求是總體積不得超過12M,主包及單個分包體積不得超過2M。支付寶對於小程式包體積的計算方式雖和微信略有區別,不過整體也大同小異。
18年至19年初時,滴滴出行小程式裡承載的業務只有網約車,且業務需求較少,在主包內都能夠搞定。而在下半年時,為了將微信錢包/支付寶九宮格入口遷移至小程式,小程式開始新增諸如公交/代駕/車服/單車/順風車等眾多業務線,同時網約車的業務需求也要做全面的補齊,業務量和程式碼量一起爆炸式增長。
滴滴出行包含了豐富多樣的出行業務,包含了快車/專車/計程車/豪華車/拼車/單車/代駕/順風車/公交/車生活等眾多業務線。整個滴滴出行小程式的最重要,使用最高頻的頁面是首頁與訂單詳情頁,首頁中承載了各個業務線的需求表達,各個業務線的訂單詳情頁則承載了具體的出行訂單展示邏輯。此外還有各種功能頁面比如個人中心,營銷頁面,設定,歷史行程。
按照滴滴出行的產品邏輯,所有業務線的需求表達邏輯都在首頁承載,為了良好的切換體驗,在首頁採用了單頁頂導的方案進行業務線展示。即每個業務線在首頁中提供一個需求表達元件,當使用者切換頂導業務線後,切換出對應的業務線元件。
在這種設計下,所有的業務線的需求表達邏輯都集中在首頁這個單一頁面中,導致在業務迭代過程中,承載首頁的主包體積迅速增長,很快觸碰了小程式平臺的單包2M上限,對後續的業務迭代與發展帶來巨大阻礙。因此,對於包體積的控制是我們在小程式開發過程中面臨的一大難題。
體積控制
下面我們將介紹滴滴出行小程式開發迭代過程中,我們對於小程式包體積進行的一系列優化控制實踐。
基礎優化手段
對於小程式來說,基礎的包體積優化手段包括:資源壓縮/去除程式碼冗餘/資源CDN化/非同步載入
在web開發中,webpack提供了大量的程式碼優化能力,包括依賴分析、模組去重、程式碼壓縮、tree shaking、side effects等,這些能力可以方便地完成資源壓縮和去除程式碼冗餘的工作。滴滴出行小程式基於滴滴開源的小程式框架Mpx( https://github.com/didi/mpx )進行開發,Mpx框架的編譯構建完全基於webpack,相容webpack內部生態,天然可以使用上述能力對包體積進行優化。
小程式中支援部分靜態資源(如影像視訊等)使用CDN地址載入,我們會盡可能將相關的資源壓縮後放到CDN上,避免這部分資源對包體積的佔用。
小程式場景下無法像web當中通過script標籤便捷地進行非同步載入,但是小程式平臺後期紛紛支援了分包載入的方案來實現該能力,由於分包載入是小程式特有的技術規範,webpack無法直接支援,因此Mpx框架專門針對該技術規範進行了良好的適配支援,關於該能力的應用我們會在後文詳細闡述。
除此之外,Mpx框架還針對小程式場景進行了許多包體積優化的適配工作,如儘可能減少框架執行時包體積佔用(壓縮後佔用56Kb),對引用到的頁面/元件按需進行打包構建,宣告公共樣式進行樣式複用,分包內公共模組抽取等。
在Mpx框架的這些能力的支援下,基本不需要額外配置就能構建出一個經過初步優化的小程式包。
微信開發者工具選項裡也有類似的"上傳程式碼時自動壓縮混淆"可勾選,但在開發者工具中上傳程式碼時計算體積是直接計算的當前專案程式碼的體積,並不會依據壓縮後的體積。因此,如果你使用原生小程式進行開發,你的source程式碼極有可能進行進一步的壓縮以節省空間。
分析體積
雖然框架已經提供了很多在體積控制方面的優化,但是隨著業務迭代我們發現主包體積依然偏大。
在遇到主包體積偏大後,我們需要弄明白,主包裡有哪些東西?它們為什麼這麼大?
使用原生小程式或者其他非基於webpack的框架進行開發的同學遇到這個問題後,可能只能去看硬碟上的檔案大小。這樣一來,各個模組的大小佔比可能並不直觀。而我們則可以藉助 webpack-bundle-analyzer 這樣一個webpack外掛去做輔助分析。
比如這是一個使用Mpx框架編寫的demo,通過 npm run build --report
就可以看到這樣一個介面:
可以看到這個demo工程由 moment / lodash / socket-weapp / core-js 等第三方庫組成。各個庫的大小,相互依賴關係也能清晰地看出。
對於滴滴出行小程式也是能看到類似的圖,能看到整個專案到底是由哪些程式碼組成。
另外,滴滴出行前端開發一直是採用“原始碼編譯”的,可以讓整個專案裡公共的依賴可以實現僅有一份,一起共用。簡而言之,也有助於減少專案程式碼體積。相關資料:https://github.com/DDFE/DDFE-blog/issues/23
要完美髮揮原始碼編譯的效果,需要上下游一起建立整套原始碼編譯生態,比如主專案的依賴方在宣告公用依賴時,就應該用peerDep或者devDep來宣告一些公有依賴,這些共有依賴應該在主專案中統一宣告,避免因版本不同裝出兩份公共依賴,那樣反而會增大體積。由於滴滴出行小程式涉及業務線及團隊眾多,部分團隊可能並不知道這個事情,因此程式碼裡實際是可能出現上述劣化場景。而依照分析圖,可以容易地發現這種問題,並推動相關團隊清除這些重複依賴。
同時,我們依照體積分析圖,對其中體積較大的檔案重點分析,進行了一輪業務程式碼梳理和精簡,刪除了一些無用程式碼,精簡了websocket的訊息體描述檔案等。
配置分包
分包是小程式給出的類似web非同步引入的一個方案,把一些初始進入時不需要的頁面可以放進分包裡,跳轉到對應頁面時才去下載分包,將這些頁面及其附屬資源放到分包裡可以有效減少主包體積。
Mpx框架早期對分包規範進行了初步支援,資源訪問規則保持和微信一致,主要根據資源存放的目錄判斷應該輸出到主包還是分包。有這個能力後,我們把行程頁抽到了分包,大概抽出了200多K左右的空間。
有了行程頁的成功拆分後,我們開始對所有的非首頁程式碼進行分包操作,比如起終點選擇和個人中心。以及部分業務線的接入是通過npm的方式接入,我們也儘可能將這些業務線的所有非首頁的程式碼放到了分包。
這裡還有個題外話,得益於mpx早期設計了packages形式的業務組合方案,可以很方便地讓業務獨立開發,又能及其方便地整合。而後發現微信的分包的json配置設計和packages很像,就在這個基礎上支援了微信的分包,使用者側僅需在原來的packages基礎上加一個query標記這個分包的名字即可。
拆除各個分包後,整個專案結構大概如圖:
初階的分包工作進行完畢後,總計從主包裡拆了差不多400K的空間到分包裡。
分包資源精細化管理
上面提到,Mpx框架初期的分包處理規則是完全按照微信的方式,把在分包路徑下的資源收集到分包裡。而npm管理的資源因為都在node_modules目錄下,不屬於任何分包路徑,則會被全部收集進主包。
比如之前我們有行程頁分包,行程頁自有的狀態管理store整個都在行程頁分包的路徑下,就會被收集到行程頁分包中。而行程頁還用到了封裝好的didi-socket庫,這個庫是公共的npm包,即使它只在行程頁分包裡被使用,但由於它本身路徑是在node_modules下的,那麼就會將其收集進主包裡。
因為早期的一些設計,行程頁的資源和首頁是分割開的,都比較獨立地存在於各自的路徑下,一期的分包處理的大頭也主要是行程頁,它剛好契合了Mpx初期對分包處理上的特點,因此能較好地收集進行程頁分包裡。
隨著業務迭代,後續大量業務線的接入都是通過npm進行的,就會有大量npm包資源,他們都在node_modules目錄下,因此全部會被收集進主包。
所以Mpx框架進行了一系列改造:
- 在構建的依賴收集過程中,我們會對收集到的依賴打上標記,記錄它是被哪些分包引入的。一旦它只有一個分包引入,它就會被輸出到這個分包中。
- 我們會根據使用者定義的分包配置,自動在 SplitChunksPlugin 中生成各個分包的 cacheGroups ,把分包中的複用模組抽取到分包下的bundle中。
- 對於元件和靜態資源,如果他們被多個分包所引用且未在主包中引用,為了確保主包體積最優,這些資源將產生多份副本分別輸出到對應分包中,而不會佔用主包體積。
這樣一來,不管分包中引用的資源原本在什麼位置,最終輸出時都會盡可能將其輸出到dist的分包目錄下,避免佔用主包空間。
這個改動完成後專案結構看似和之前一樣,但得益於Mpx處理分包資源能力的升級,我們得以將業務線分包中引用的npm資源成功輸出到其所在的分包目錄下。
封面方案
再後來滴滴出行小程式需要替換微信/支付寶裡原有的WebApp入口,小程式接入的業務線迅速增加,包體積迅速增長。
這個部分體積增長的主要原因前面提到過,所有的業務線都要接入到主頁來展示。這也是由於業務特點決定的,滴滴出行提供了豐富的出行產品線,包括快車/專車/計程車/豪華車/拼車/單車/代駕/順風行車等產品,使用者是可能需要反覆切換挑選的。這個過程還要保留起終點車型之類的資訊,必須是一個頁面內切換元件加一整套非常複雜的大型狀態管理才能比較流暢順滑地實現。而不能像一些電商/資訊平臺,將不同的功能拆分到不同頁面,讓使用者通過首頁的選單進入子頁面再進行操作,首頁只承載入口,只有較少的業務邏輯,分包處理起來就會容易很多。
因此各個業務線都要提供首頁元件進行接入。這個元件會在首頁被用到,所以無論如何也拆不到分包裡。最終,整個首頁主包部分的體積可以分成兩個部分:基礎庫和業務程式碼。兩者的體積佔比大概是公共依賴基礎庫佔1M左右,業務程式碼佔1M左右。
這麼龐大的基礎庫體積主要是由於滴滴出行的業務線及業務團隊眾多,各方均有一些自己的基礎依賴。比如網約車依賴的長連結通訊pb資料描述檔案,地圖會依的大數計算庫,順風車依賴的CML框架執行時、代駕依賴的通訊閘道器庫,以及公用的元件庫和polyfill等。
所以滴滴出行小程式面對的問題在當時已經無法用純技術方案在短期內快速解決問題了,於是我們做了一個工程架構調整,可以叫封面頁方案,解決了主包問題。
封面方案簡單講,就是做一個帶滴滴出行Logo的封面作為啟動頁面,而頁面一旦載入,立刻跳轉另一個頁面,這個頁面真正承載業務,且它被放在分包裡。
這個操作的意義在於,主包裡就只剩下了所有方都要依賴的基礎框架/庫等,而業務全被抽離到了分包裡。
這是封面方案完成後專案的結構圖,之前很大塊的首頁業務邏輯被抽出到首頁分包中了。
這樣一個挪移的操作的結果是我們可以有2M的主包空間來乘放基礎的公共的庫,有一個2M左右的分包來乘放前面提到的滴滴出行的整合了各種業務的“大主頁”。而當時拆下來差不多有1.2M的主包,800K+的業務主分包。
這個改造最優秀的一點在於,後續的業務迭代產生的體積增長几乎全是在業務主分包裡,剩下的1.1M+空間留給業務迭代還是比較充裕的。而主包的體積在理想條件下是可以長久保持不變的,就不會因為業務需求的不斷開發反覆導致主包體積臨近超標,不再需要為主包體積感到焦慮。
當然,可以看到,這個方案本身是沒有消減任何體積的,只是把位置變換了一下。除此之外,這個封面頁方案其實也存在一些缺陷,比如首屏業務的展示會變慢,因為要載入的內容會變多,不過小程式本身有較好的快取資源的能力,因此還算可以接受。
相比於因體積問題卡住需求迭代以及產品線的接入,目前這個方案至少能解決有無問題。我們開發團隊後續也會持續跟進關注體積問題,看是否會有產品方案變更或者小程式本身給出一些解決方案來進一步優化這個部分。
總結
Mpx框架在包體積控制上做了大量工作,對於npm場景下的小程式分包進行了非常完善的支援。
滴滴出行小程式團隊在框架支援的基礎上,通過梳理業務依賴,充分利用分包,調整互動方案等一系列手段,在不阻礙業務發展的前提下,將龐大複雜的滴滴出行小程式包體積控制在平臺限制範圍內。
希望本文能給在包體積上遇到問題的小程式開發者們帶來一些啟發,歡迎留言交流。