2023年通天塔搭建頁前端效能最佳化階段分享

碼農談IT發表於2024-02-01


來源:京東技術

導讀

本文分享京東集團2023年通天塔專案在搭建頁前端效能最佳化的經驗和成果。讀者將瞭解如何透過減少頁面載入時間、提高互動響應速度和執行效率,來顯著提升使用者體驗。文章還將涵蓋對工具和實踐方法的評估,以及如何量化效能改進的效果。




01 
前言


在今年的敏捷團隊建設中,我透過Suite執行器實現了一鍵自動化單元測試。Juint除了Suite執行器還有哪些執行器呢?由此我的Runner探索之旅開始了!

通天塔搭建頁專案是用來搭建各類活動頁面,比較老且業務複雜的專案,可最佳化點還是非常多的。今年側重對運營頁首屏載入的效能最佳化,在保證系統穩定可控、需求持續迭代前提下,最終提升了58.8%速度。在此非常感謝通天塔產品組、後端組、前端組同學,對專案效能最佳化大力支援。

2023年通天塔搭建頁前端效能最佳化階段分享圖 1.
回顧一年的不斷探索,得出的感受的是:
選擇大於努力,努力的方向不對,想取得成果就會越來越費勁,事倍功半;方向選對了,事半功倍。
效能最佳化是長期的工程,需要優先確立正確的分析方法,真正且更早地找出系統的癥結所在,而不是想當然或者僅停留於表面現象來下判斷。

市面上有很多效能最佳化方案,數不勝數,但如果開始就只是模仿一些邊邊角的最佳化,雖然也會略有效果,但不一定能給系統解決核心卡頓問題,不能給系統帶來質的提升,甚至到後面最佳化效果越來越差,這些最佳化可能會衝突、可能變成負最佳化。而且如果不去優先分析核心的問題,可能就會一直忽視核心問題,導致系統長期處於低效低體驗的狀態,隨著業務複雜變得越來越卡。



02 
  

常見分析工具

  


理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板後進行模板載入,載入階段會將產物轉換為檢視樹的結構,轉換完成後將透過表示式引擎解析表示式並取得正確的值,透過事件解析引擎解析使用者自定義事件並完成事件的繫結,完成解析賦值以及事件繫結後進行檢視的渲染,最終將目標頁面展示到螢幕。
1、網路分析功能Network
分析不同型別資源大小、優先順序、時間細分資料、請求和響應詳情、快取利用情況。還可以模擬不同網路環境。
https://developer.chrome.com/docs/devtools/network?hl=zh-cn

2023年通天塔搭建頁前端效能最佳化階段分享

圖 2.

瀑布流Waterfall比較直觀看出,資源等待時間、載入時間、載入時機,對頁面整體載入有一個總覽。
2、效能皮膚Performance

分析網頁在執行時(而不是載入)時的效能。具體分析頁面每個階段哪些方法在執行,哪些資源在載入,很容易看出哪裡在阻塞。

也可以用來設定不同的cpu和網路環境。由於開發者電腦一般效能比較好,模擬較差效能和網路也是有必要的。

https://developer.chrome.com/docs/devtools/performance?hl=zh-cn

2023年通天塔搭建頁前端效能最佳化階段分享

圖 3.

官網給出分析順序是,

1)先看摘要Summary標籤頁,分析到底是載入、執行指令碼、渲染、繪製哪一個最佔時間,再去決定主要最佳化方向。

2023年通天塔搭建頁前端效能最佳化階段分享圖 4.

如果是載入速度慢,再去分析網路、資源大小、載入優先順序、快取等。
如果是執行指令碼慢,再去分析程式碼執行順序,看哪些任務執行時間長或者執行次數過多。
如果是渲染速度慢,還要具體分析,
(a)渲染dom太多則可以考慮虛擬滾動
(b)圖片的原因則考慮最佳化圖片格式、壓縮圖片大小、圖片懶載入、低質量圖片過渡、圖片非同步解碼
(注:最佳化圖片格式是指選擇壓縮率高、解碼速度快、同時畫質良好的圖片格式)
(c)重新計算樣式導致強制自動重排,需要儘量避免大型、複雜的佈局和佈局抖動;

https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing?utm_source=devtools&hl=zh-cn#avoid-forced-synchronous-layouts

2023年通天塔搭建頁前端效能最佳化階段分享

圖 5.

還有其他方式可專注最佳化可視區域的渲染。

2)再展開主要Main部分。開發者工具會向您顯示主執行緒上的活動隨時間變化的火焰圖。x 軸表示一段時間內的記錄。每個條形都代表一個事件。橫條越寬,表示事件用時越長。y 軸表示呼叫堆疊。如果您看到事件相互堆疊,則表示上層事件導致了下層事件。

2023年通天塔搭建頁前端效能最佳化階段分享圖 6.

3)放大上方Overview,找到任務右上角的紅色三角形,定位到耗時較長的任務,再定位到具體的方法中。

2023年通天塔搭建頁前端效能最佳化階段分享

2023年通天塔搭建頁前端效能最佳化階段分享圖 7、8.

對於長任務,官網還有進一步最佳化建議:

https://web.dev/articles/optimize-long-tasks?utm_source=devtools&hl=zh-cn

懶載入相關建議:

https://developer.mozilla.org/zh-CN/docs/Web/Performance/Lazy_loading
3、打包分析webpack-bundle-analyzer
主要分析js打包,檢視具體打包情況,考慮如何對體積過大的包“瘦身”。
常用手段有懶載入、全域性引入第三方外掛、刪除冗餘包、刪除重複打包。

2023年通天塔搭建頁前端效能最佳化階段分享

4、程式碼覆蓋率Coverage

可以分析不同資源使用率情況。對於低使用率且較大體積的檔案,考慮懶載入、移除無效程式碼。

2023年通天塔搭建頁前端效能最佳化階段分享圖 9.

5、燈塔皮膚Lighthouse

Lighthouse 是 Google 開發的一款自動化的開源工具,用於提升網頁質量。您可以針對任何公開網頁或需要身份驗證的網頁執行它。它包含對效能、無障礙功能、漸進式 Web 應用、搜尋引擎最佳化 (SEO) 等方面的稽核。

https://developer.chrome.com/docs/lighthouse/overview?hl=zh-cn

2023年通天塔搭建頁前端效能最佳化階段分享圖 10.

主要是給頁面綜合評分,還有FCP、LCP、TBT、CLS分析。如果評分還有不足的地方,那就還有較大最佳化的空間。

也可以像 webpack-bundle-analyzer 裝在本地執行。


03 
 主要最佳化方向

 



理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板後進行模板載入,載入階段會將產物轉換為檢視樹的結構,轉換完成後將透過表示式引擎解析表示式並取得正確的值,透過事件解析引擎解析使用者自定義事件並完成事件的繫結,完成解析賦值以及事件繫結後進行檢視的渲染,最終將目標頁面展示到螢幕。    

3.1  加速請求資源速度


    
1)充分利用快取

個人體驗感覺,快取帶給頁面效能提升是最明顯的。

資源類:http快取、cdn快取、service worker;
資料類:cookie、localStorage、sessionStorage、indexDB;

像base64的圖片可以當資料類儲存。

還有函式內部的快取;

2http相關最佳化
http/https1.0、1.1版本下需要減少同域名下的請求
不同瀏覽器支援併發的情況不一樣,比如chrome瀏覽器http/https1.0、1.1版本下有併發請求限制,在同一域名情況下:
- 同一get請求的併發數是1,即只有上一個請求結束,才會執行下一次請求,否則即在佇列中等待請求;
- 不同的get/post的請求的併發數量是6個,當達到6個時,其餘的在隊裡中等待請求;
所以需要把資源分佈放在多個域名下,類似cdn1.jd.com,cdn2.jd.com,cdn3.jd.com。
如果有條件儘量升級http2或者http3,可實現多路複用:透過該功能,在一條連線上,瀏覽器可以同時發起無數個請求,並且響應可以同時響應。另外,多路複用中支援了流的優先順序(Stream dependencies)設定,允許客戶端告知伺服器最優資源,可以優先傳輸。
升級後還有其他優點,比如也會對請求頭壓縮,進一步提升請求速度。
根據 Cloudflare 截至2023年4月的統計,目前最普及的協定仍是HTTP/2,市佔率超過60%。
3)預解析資源Prefetch
html程式碼里加入這樣的 link 標籤實現預解析,類似以下:



<link rel="dns-prefetch" href="//cdn1.jd.com"><link rel="dns-prefetch" href="//cdn2.jd.com"><link rel="dns-prefetch" href="//cdn3.jd.com">
普遍來說合理的dns prefetching能對頁面效能帶來50ms ~ 300ms的提升。
4)預載入資源Preload

比如字型檔案


<link rel="preload" as="font" href="xxxx.woff" />
5)所有資源走CDN

不止是靜態資源,有條件的話,儘量讓所有資源都走CDN,有效提升載入速度,減少白屏時間。

3.2  減少請求資源大小


    
首先,我們先用分析下哪些模組程式碼比較大,需要最佳化。
1)儘量按需載入模組,儘量避免整個包都引入
比如:
(a)一些比較明顯的全域性引入,程式碼裡存在的*引入。



import * as xxxx-ui from 'xxxx-ui'
const Button = xxxx-ui['Button']

最好都改成按需載入,確保打包時候不會把整個庫都打進去。


import { Button } from 'xxxx-ui'

只要支援基於 ES modules tree shaking,就會有按需載入的效果。

不過有的引入比較隱蔽,不容易直接發現,只有進行打包分析後才能實際發現多種情況打包了多餘的包。

(b)引入子包寫法不正確

比如引入加密包不正確,引入導致打包體積很大。




import crypto from 'crypto'const str = 'xxxxxxxxx'const sign = crypto.createHash('md5').update(str).digest('hex')

實際只用了crypto的md5方法,但卻把整個crypto包和其依賴的包都打包進程式碼裡。

2023年通天塔搭建頁前端效能最佳化階段分享圖 11.

後續改成只引入crypto的md5方法就能起到一樣的效果。




import md5 from 'crypto-js/md5' const str = 'xxxxxxxxx'const sign = md5(str).toString()


就可以把上面這個js裡絕大部分的程式碼都省去了,最後把這個js在Gzipped下從190KB減少到3KB。

類似的lodash也有這樣的情況,透過改變寫法,也可以改善打包大小,在Gzipped下從幾十KB減少到幾KB。

2023年通天塔搭建頁前端效能最佳化階段分享圖 12.

只需改寫下,單個引入lodash裡的方法

2023年通天塔搭建頁前端效能最佳化階段分享
2023年通天塔搭建頁前端效能最佳化階段分享

圖 13、14.

比如,antd3.0的元件引用也有類似問題,需要改寫引用程式碼按需載入元件;

比如,僅修改通天塔列表cms專案裡所有的data-kit引入方法,就讓整體打包js體積縮小了50%;

2023年通天塔搭建頁前端效能最佳化階段分享圖 15.

(c)引入某個包的樣式檔案也有可能導致整個包都打包進來
比如打包分析和排查程式碼時,發現 braft-editor這個編輯器實際沒有使用了,但引入樣式的程式碼沒註釋掉,導致整個braft-editor包都被打包進來。後續註釋掉,直接節省Gzipped下114KB體積。


// 引入編輯器樣式import 'braft-editor/dist/index.css'
2023年通天塔搭建頁前端效能最佳化階段分享
圖 16.
注意:如果發現按照上述幾種方法,修改了引入方法,但打包分析裡依然還是完整引入,那麼這裡情況就會比較複雜,只能上網找類似問題了。有可能是想不到的其他地方隱晦全量引入,也有可能是寫法還是不對,也有可能是打包配置不對,甚至有可能就是那個包的版本實際不支援按需引入。
2)不重要的程式碼模組懶載入(dynamic import)
比如 React.lazy
常規引入:


import Header from './Component/Header'<Header />
懶載入處理後:




const Header = React.lazy(() => import(/* webpackChunkName:"Header" */ './Component/Header'))<React.Suspense fallback="">    <Header /></React.Suspense>
fallback可以為空,也可以用一些Spin(載入中)、Skeleton(骨架)或者簡化業務元件來佔位、過渡。

注意1:還是不能濫用,主要針對低優先順序或者體積過大的模組、元件。

注意2:實際發現setTimeout也可以懶載入一些檔案,不過不太推薦使用。

setTimeout(() =>  import(/* webpackChunkName: 'xxx'*/ './xxx.less' ), 500)
3)使用更精簡的程式碼或者包替代原先的包
(a)原生方法或者手寫方法(保證效能的前提下)
比如,在安全性不重要的場景下,可以只用原生的編碼方法atob、btoa或者再封裝的方法來替代加密包的方法;
(b)更小的包

比如,使用 Day.js 替換 momentjs 最佳化打包大小,但是打包多一個配置,這是antd裡介紹的配置;

2023年通天塔搭建頁前端效能最佳化階段分享圖 17.

4)不重要的外掛改成全域性引入第三方外掛
根據需要適時請求,外掛方法掛在window全域性下,避免產生直接依賴。
同時給script標籤加上 async 或者 defer屬性,避免指令碼阻塞頁面。
(從表現形式上來說,async 的優先順序比 defer 高,也就是如果同時存在這 2 個屬性,那麼瀏覽器將會以 async 的特性去載入此指令碼。)

<script src="

比如這個生成excel檔案的外掛,打包在專案裡就會很重。放在cdn上,不隨專案打包版本更新,每次載入後還能有較長快取時間。

2023年通天塔搭建頁前端效能最佳化階段分享圖 18.

注意1:有的時候,確實把第三方包抽出來了,但是呼叫方法時,還是使用import引用了,這時可能存在第三方包沒載入導致的import報錯,此時需要嚴格控制時機。個人建議非關鍵的包不輕易使用webpack的externals,一般就掛在window下,不產生直接依賴,可以等較晚時候直接呼叫(建議呼叫時,trycatch包一下,呼叫出錯就看下面兜底方案);
注意2:第三方的cdn,可能會在有的區域、有的網路載入不出來資源。這時可能要考慮載入失敗時,重新載入或者切換備用的下載連結。包括上面的懶載入,都有資源載入失敗的情況。對於慢網和cdn故障等情況,要儘可能做多重兜底;
推薦一個兜底方案的連結:
5)壓縮資源大小
(a)壓縮圖片大小;

- 直接壓縮工具壓縮;

- 物件儲存(OSS)壓縮或者裁剪圖片;

- 版本較高瀏覽器使用webp格式圖片;
(b)Gzip壓縮(Gzip 對一般純文字內容可壓縮到原大小的 40%);

- nginx伺服器中配置;

- http2頭部壓縮;
(c)Brotli壓縮(Brotli 的效能相比 Gzip 提高了 17-25%);
注意:使用 Brotli 壓縮所有資源非常耗費計算資源和時間,在最高壓縮級別下,會讓伺服器等待動態資源。伺服器開始傳送響應所花費的時間會抵消檔案大小減少帶來的任何潛在收益,也就是說會延長 TTFB 的時間。
(d)第三方外掛都使用壓縮版的;
(e)壓縮html檔案(包含其中內聯的script、style程式碼);
比如,配置 HtmlWebpackPlugin 的 minify。
6)放棄對老瀏覽器支援
@babel/polyfill為了彌補低版本瀏覽器中缺失的特性,會導致打包體積變大。就算配置 按需注⼊(useBuiltIns: "usage"),還是會比較大。有條件還是早點放棄低版本瀏覽器。
7)刪除多餘的語言包

多數語言包是不需要被打包進來的,可以打包分析檢查一遍。

2023年通天塔搭建頁前端效能最佳化階段分享

圖 19.

8)字型檔案壓縮

一個完整字型檔案都有幾MB,而一般專案裡只有少數文字需要用到特殊字型,可以利用類似Fontmin把需要的文字單獨拎出來。

如果字數較少也考慮圖片替代,和其他圖片合併。

9)打包移除多餘程式碼

(a)Tree Shaking 刪除多餘程式碼

webpack3可以配置,webpack4+的mode: procution下自帶。其他打包工具也有支援。

注意1:使用 ES2015 模組語法(即 import 和 export)。

注意2:確保沒有編譯器將 ES2015 模組語法轉換為 CommonJS。比如lodash就是基於commonjs,所以才有上文《減少請求資源大小》中把整個lodash包都打進去的情況。

注意3:在專案的 package.json 檔案中新增 "sideEffects" 屬性。

(b)移除生產環境的 console.log、debugger、註釋









new UglifyJsPlugin({    uglifyOptions: {        compress: {            drop_console: true,            drop_debugger: true        }    }})

10)生產環境選擇適當source-map方案,控制打包體積

根據實際情況,需要考慮原始碼要不要隱藏,除錯要不要更友好。

3.3  減少請求資源數量


    
1)js類

合併js請求:配置webpack裡splitChunks的cacheGroups,把必用的公共依賴打包到一起,類似:

2023年通天塔搭建頁前端效能最佳化階段分享圖 20.

2)圖片類

(a)多個圖片合併成雪碧圖;

(b)圖示類圖片儘量使用向量圖示庫iconfont(儘量按需配置、按需打包);

(c)可css、svg繪製的,儘量用css、svg實現;

(d)非可視區域的圖片懶載入;
比如利用loading屬性

<img loading="lazy" src="image.jpg" alt="..." />
3)埋點或者不緊急不重要的上報延遲傳送
埋點的資訊,可以先全域性存起來,等頁面基本載入完成後,再載入埋點外掛,上報埋點資訊。減少首屏情況下,傳送大量埋點上報。

3.4  最佳化程式碼邏輯


    
1)業務程式碼邏輯

(a)有些資料會非常大,儘量分頁優先請求或者載入渲染涵蓋可視區域範圍前幾個、前十幾個,讓首屏的展示速度更快一點,後續再用完整資料覆蓋或者增量渲染;

(b)非許可權類的請求可以放在更早的位置發出,非阻塞性的請求可以先請求再等較晚時候處理邏輯,業務優先順序低的請求可以放在頁面渲染完成以後發出;

(c)不同優先順序的元件,可以分不同階段載入、處理邏輯,讓關鍵模組優先渲染、頁面整體過渡效果更好;

(d)如果存在前置的頁面,可以在前置的頁面空閒時間提前載入後續頁面的資料,甚至是資源;

(e)複雜的渲染和資料處理,也可以考慮遷移到服務端來做;

(f)全域性引入的第三方外掛如果有業務場景限制,可以按需動態載入,而不是每次載入所有第三方外掛。理論上,其他程式碼也可以這樣處理,只是同一路由下,有一定難度,比如服務端按需渲染。
2)通用程式碼邏輯

(a)迴圈、遞迴巢狀層級太深太多的話很容易造成卡頓;

(b)迴圈使用時,確認是否可以提前中斷迴圈,而不是把每個迴圈都走完;

(b)有的方法比較耗費效能,類似深複製、字串拼接,注意使用次數;

(c)檢視下手寫的方法是否可以用lodash等成熟庫的方法替代,可能效能更好;

(d)不同模組裡相同或者相似的程式碼,提取成公共方法或者元件;

(e)監聽器、計時器最好控制數量,配套退出機制,及時清除;

(f)高頻觸發事件,最好用防抖和節流;
以上都可以用效能分析(Performmance)、列印埋點、大量資料驗證最佳化效果,可以適當評估實際效能差異,再做取捨。

3.5  最佳化業務邏輯


    
一個成熟專案應該是,由合理產品規劃、穩定技術架構、統一設計互動組成的。這三個方面,都應該是最佳化專案的方向,而不是單純技術思路。

(1)對於大部分專案,其實已經做過或多或少的最佳化,但可能依然有卡頓。

(2)對於大部分文章,其實主要集中於純技術,也都很少去思考產品規劃是否合理、業務邏輯是否合理。

(3)對於大部分開發,其實也是更關心如何更好實現技術,很少參與產品規劃和設計互動。
單個頁面承載功能是有限的,首屏載入的資源和資料也是有限的,普通使用者的網路和裝置效能也是有限的。
可以考慮以下幾點:

(a)針對大多數使用者使用習慣,優先提供簡化易懂易用的常用互動,同時把專業垂直複雜的冷門互動單獨拎出來;

(b)有的業務模組邏輯非常重的,可以獨立出來,或者拆分成多級、多個模組;

(c)對於功能繁雜的頁面,應該把不重要的業務模組遷出、收起、延後展示;

(d)透過埋點確認沒人使用的業務模組,應該考慮下線;

(e)低使用頻率的功能控制載入次數,類似版本更新功能,每次開啟頁面都會請求最新配置介面資料,可以設定間隔一段時間才能重新請求,或者跟隨版本號更新後才能重新請求一次;

(f)分析使用者群體絕大部分的常用裝置、常用瀏覽器,有意引導使用者使用現代瀏覽器,逐步放棄對低版本裝置、低版本瀏覽器支援;
總有一些問題,需要開發來推動業務改動吧。


04 
  總結  


理解,首先 MCube 會依據模板快取狀態判斷是否需要網路獲取最新模板,當獲取到模板後進行模板載入,載入階段會將產物轉換為檢視樹的結構,轉換完成後將透過表示式引擎解析表示式並取得正確的值,透過事件解析引擎解析使用者自定義事件並完成事件的繫結,完成解析賦值以及事件繫結後進行檢視的渲染,最終將目

通天塔搭建頁是用來搭建京東商城各類活動頁面,整個專案迭代時間很久遠且業務複雜。隨著體量上升,載入速度慢、操作卡頓現象越來越多,效能最佳化成了23年的當務之急。通天塔及互動前端組經過一年的全方位最佳化,提升了58.8%首屏載入速度,兼顧了體驗和功能,在此分享一下這一年的經驗和心得。

來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024924/viewspace-3006004/,如需轉載,請註明出處,否則將追究法律責任。