商智C店H5性能優(yōu)化實戰(zhàn)
前言
商智C店,是依托移動低碼能力搭建的一個應用,產(chǎn)品面向B端商家。隨著應用體量持續(xù)增大,考慮產(chǎn)品定位及用戶體驗,我們針對性能較差頁面做了一次優(yōu)化,并取得了不錯的效果,用戶體驗值(UEI)從一般提升到良好。本文詳細記錄了優(yōu)化思路及過程,期望給正在或打算做用戶體驗提升的小伙伴提供一些參考。
一、性能優(yōu)化概覽
從宏觀方面講,前端性能優(yōu)化主要包含兩個層面:
指的是從用戶輸入URL到頁面完整渲染出來的過程中,通過減少資源請求時長、合理化頁面渲染等方式,讓頁面內容盡快呈現(xiàn)到用戶面前,達到舒適的展示效果。
指的是在資源加載且頁面渲染完成后,用戶與頁面的交互反饋及時且動效流暢,沒有卡頓、掉幀等情況。
本文重點介紹前者,性能監(jiān)測工具依賴燭龍平臺(性能指標來自LightHouse性能工具)。
二、性能分析
1、Lighthouse工具
Lighthouse 是 Google Chrome 推出的一款開源自動化工具,它可以搜集多個現(xiàn)代網(wǎng)頁性能指標,分析 Web 應用的性能并生成報告,為開發(fā)人員進行性能優(yōu)化提供參考方向。
1.1 性能指標
首次內容繪制(First Contentful Paint),是指測量在用戶導航到頁面后瀏覽器呈現(xiàn)第一段 DOM 內容所花費的時間。DOM 內容包括頁面上的圖像、非白色<canvas>元素和 SVG 等,不包括 iframe 內的任何內容。
最大內容繪制(Largest Contentful Paint):測量視口中最大的內容元素何時呈現(xiàn)到屏幕上,這近似于頁面的主要內容對用戶可見的時間。
速度指數(shù)(Speed Index),反映了網(wǎng)頁內容填充的速度。頁面解析渲染過程中,資源的加載和主線程執(zhí)行的任務會影響到速度指數(shù)的結果。
累積布局偏移(Cumulative Layout Shift),主要測量可見元素在可視區(qū)域內的移動情況。該指標需要重點關注,隨著Lighthouse的版本迭代,該指標占比一直呈上升趨勢,也是頁面性能優(yōu)化容易忽略的方面。以下為Lighthouse不同版本中CLS指標的占比情況:

總阻塞時間(Total Blocking Time),是指測量頁面被阻止響應用戶輸入(例如鼠標點擊、屏幕點擊或鍵盤按下)的總時間。它是通過添加 FCP 和 TTI 之間所有長任務的阻塞部分來計算總和,任何執(zhí)行時間超過 50 毫秒的任務都是長任務。
各指標表現(xiàn)評價標準:

1.2 UEI計算規(guī)則
UEI指的是用戶體驗值,燭龍平臺底層使用的是Lighthouse性能統(tǒng)計工具。其中,單頁面的得分統(tǒng)計規(guī)則與其保持一致(性能指標分數(shù)的加權平均值)。針對多頁面應用,整體評分計算規(guī)則為,每個頁面得分的加權平均值:(頁面A * A訪問量 + 頁面B * B訪問量 ...)/ (A訪問量 + B訪問量 + ...)。
2、Chrome調試工具
Chrome的Performance面板,可以記錄頁面加載時\運行時的所有活動,方便我們找出頁面的性能瓶頸。

以上為performance面板的概覽圖,通過對頁面加載過程的錄制,查看視圖面板的主線程工作過程,我們能夠找到頁面阻塞的長任務,從而優(yōu)化TBT指標;交互階段存在的動畫卡頓等問題,也可以通過該面板進行分析和優(yōu)化。
三、實戰(zhàn)
1、項目介紹
商智C店,是依托移動低碼搭建的面向B端商家的應用。根據(jù)燭龍數(shù)據(jù)統(tǒng)計,優(yōu)化之前,用戶體驗值在58分左右。我們期待通過本次優(yōu)化,將燭龍統(tǒng)計得分提升到體驗良好的水平(75分及以上)。從下圖可以看出,頁面的CLS、TBT分數(shù)很低,LCP指標分數(shù)位于性能良好與性能較差之間,這三個指標是我們接下來進行優(yōu)化的重點。

2、過程記錄
2.1 減小資源體積
2.1.1 抽取公共依賴
商智C店采用了主應用加三個微應用的架構,這三個微應用的包大小為:

可以發(fā)現(xiàn),每個包的體積都很大(5000k以上),這勢必會影響資源下載速度并拖慢代碼執(zhí)行效率。微應用包體積過大的原因是:主應用和多個微應用間存在重復打包,如組件庫(自研)、公共庫依賴(react、react-dom、echarts)等內容。
針對該問題,可在制品的生成階段做處理:生成兩個包,一為完整的包,保證其能夠獨立使用;二為優(yōu)化的包,里面剔除了公共依賴及組件庫的樣式。調整后,包的體積減少1300多k,總包大小減少至4000k左右。

需要說明的是,自研組件庫目前并沒有提供UMD格式打包方式,因此以上提到的公共依賴不包含自研組件庫相關的包(后續(xù)會處理)。
2.1.2 tree shaking優(yōu)化
tree shaking(搖樹) ,用于描述移除 JavaScript 上下文中的未引用代碼(dead-code)。最初起源于 ES2015 模塊打包工具 rollup。
自研組件庫主要包含三個包,PC組件庫jmtd、移動組件庫jmtm、jmtd-hooks庫。其中hooks庫是對兩端組件庫的二次封裝,幫助低碼平臺處理組件間的邏輯編排功能,三個庫均通過npm包的方式引入。

排除公共依賴及樣式后,微應用制品的體積大約在4000k左右,這顯然還不是一個理想值。打包構建工具依賴webpack5,根據(jù)webpack性能分析插件分析,定位到jmtd-hooks包的體積過大。按照固有思維,webpack5在生產(chǎn)環(huán)境默認支持tree shaking,因此生成的制品只會打包使用到的代碼,并不會引起制品的體積過大。那么問題出在哪里呢?

原因在于,jmtd-hooks庫的打包方式有問題。因hooks包較強的業(yè)務屬性,我們將原始文件打包成了一個文件。
webpack的tree shaking有兩種方式:
分析后可得出結論:webpack對單文件,僅利用其靜態(tài)分析能力搖樹效果很弱,導致微應用制品包含了大量未使用的代碼。解決辦法也很簡單,調整jmtd-hooks的打包方式,保留原始的文件結構。調整后hooks包的結構為:

可看到,制品體積有了大幅的減小,最終控制在2000k以下。經(jīng)過抽取公共依賴和搖樹優(yōu)化兩個步驟,整體包體積減少65%左右。

2.1.3 刪除項目冗余代碼
隨著需求持續(xù)迭代,項目中很容易存在一些無效的腳本及樣式引用,或者一些不規(guī)范的寫法,如未使用的全局變量、沒有意義的console等,都需要進行刪除,保持代碼的精簡性。
完成以上步驟后,我們進行了一次上線,并連續(xù)監(jiān)測了一周的數(shù)據(jù)。用戶體驗值有了明顯的提升,從以前的58分提升到65分。
優(yōu)化前:

優(yōu)化后:

以下為優(yōu)化前后各指標的對比數(shù)據(jù)(單位:ms):

2.2 優(yōu)化資源加載速度
一個頁面從輸入URL到最后在瀏覽器的渲染流程大致如下:
URL解析 => DNS解析 => 建立TCP連接 => 客戶端發(fā)送請求 => 服務器處理和響應請求 => 瀏覽器解析并渲染響應內容 => 斷開TCP鏈接
從URL解析到服務器響應請求的過程屬于網(wǎng)絡層面,常用的優(yōu)化手段包括:
1、緩存技術:合理的使用緩存,如CDN緩存、瀏覽器緩存等,來加快資源的下載速度。
2、文件壓縮合并:通過壓縮工具減少資源大小,多個文件合并成一個文件,減少網(wǎng)絡請求開銷。
3、開啟gzip壓縮:在文件傳輸階段,開啟gzip壓縮,減小數(shù)據(jù)傳輸量,節(jié)省服務器網(wǎng)絡帶寬。
4、http2協(xié)議:該協(xié)議帶來了很多新的特性,如二進制分幀、頭部壓縮等,把證文件傳輸更加安全高效。
5、DNS預解析:初次請求某個跨域域名,需先解析該域名,其解析時間很容易被忽視,DNS預解析能夠減少用戶等待的時間。
6、域名分片:域名分片指的是將同一站點下的靜態(tài)資源分布在不同域名下,其最大的好處是,能夠突破瀏覽器下載資源的并發(fā)限制。
7、代碼分割:將整個應用代碼合理分割成多個部分,能夠有效減小首屏資源的請求體積,提升資源下載速度。
2.2.1 代碼分割
合理的分割代碼,能夠有效加快首屏資源請求速度。出于此考慮,我們將項目結構分為了一個主應用加三個微應用的模式,主應用作為基座來處理導航及微應用間通信,三個微應用(經(jīng)營分析、行業(yè)競爭、我的關注)作為獨立模塊,承接細分的業(yè)務功能。以模塊劃分代碼,大體上解決了問題,但針對某些高頻使用的詳情頁,還有進一步優(yōu)化的空間。

根據(jù)埋點數(shù)據(jù)統(tǒng)計,用戶高頻使用的主要有兩個頁面:經(jīng)營分析頁和指標詳情頁,其訪問的PV值占到了90%以上。經(jīng)營分析頁的表現(xiàn)尚可,用戶體驗值在70分左右,可暫不優(yōu)化;指標詳情頁的表現(xiàn)堪稱災難,僅得到58分,明顯低于現(xiàn)階段65分的平均值。

分析原因:指標詳情頁為一個二級路由頁面,可通過核心指標頁下鉆到該頁,也可通過客戶端首頁直接查看某個指標的詳情頁。這將導致,在查看該頁面前,需先加載經(jīng)營分析模塊。
解決思路:將指標詳情頁獨立為一個微應用,單獨維護。當用戶通過京麥客戶端直接訪問某個指標的詳情時(用戶使用此場景頻率很高),只加載該微應用代碼,避免一級路由頁代碼的影響。
優(yōu)化后效果很明顯,指標詳情頁得分從58分直接飆升到80分,各個維度的指標都有了不錯的提升。

2.2.2 資源預加載
資源預加載是性能優(yōu)化的常用手段,針對模塊較多的應用尤其有效。前文我們已將商智C店分割為主應用加三個微應用的代碼結構,首頁加載的是主應用加經(jīng)營分析模塊,當用戶切換其它的模塊(競爭分析、我的關注等),需要先下載該模塊的制品(2000k左右),再進行內容的渲染。
采用資源預加載方案,可在首頁內容加載完成后,利用瀏覽器的空余資源,預加載其它模塊的文件,當用戶切換到其它模塊時,資源已經(jīng)提前加載完畢,省去資源下載的時間。
具體實現(xiàn)邏輯為:
// 通過動態(tài)創(chuàng)建script的方式,預加載文件
// 腳本加載并解析完成后,會將執(zhí)行邏輯掛載到全局變量上
function loadScript (url) {
if (!cache || cache[url]) return;
cache[url] {
const script = document.createElement('script');
script.src = url;
script.onload {
document.body.removeChild(script);
resolve(window.__microApp__);
};
document.body.appendChild(script);
});
}
// 當頁面加載完成后,通過requestIdleCallback方法,利用瀏覽器空余資源來預加載文件
window.onload {
microAppArr.forEach(({ src }) loadScript(src));
});
};
資源預加載之前,行業(yè)競爭模塊的得分在60分,優(yōu)化之后,提升到了68分:

優(yōu)化之前,我的關注模塊的得分在65分,優(yōu)化之后提升到72分:

2.2.3 DNS預解析
當你的網(wǎng)站第一次請求某個跨域域名時,需要先解析該域名(例如頁面訪問cdn資源,第一次訪問需要先解析cdn),而在該請求之后的請求都沒有這項時間支出。典型的一次DNS解析需耗費20-120毫秒,其解析的時間很容易被忽視。DNS Prefetching 是具有此屬性的域名,不需要用戶點擊鏈接就在后臺解析,這個方式能減少用戶的等待時間,提升用戶體驗。
<!-- 用meta信息來告知瀏覽器, 當前頁面要做DNS預解析 -->
<meta http-equiv="x-dns-prefetch-control" content="on" />
<!-- 在頁面header中使用link標簽來強制對DNS預解析 -->
<link rel="dns-prefetch" href="xxx.com">
商智C店具體應用案例:

2.2.4 域名分片
域名分片指的是將同一站點下的靜態(tài)資源分布在不同域名下。例如:主站域名 www.a.com;圖片域名 www.a-img.com;腳本、樣式等文件的域名 www.a-link.com。其最大的好處是,能夠突破瀏覽器下載資源的并發(fā)限制,具體可參考文章。
同樣在商智C店中,我們也應用了該思路,如將React、Echarts等公共庫、業(yè)務打包代碼、圖片等靜態(tài)資源分別放在不同的CDN域名,從而突破瀏覽器的并發(fā)請求限制。
2.2.4 雪碧圖
主應用的底部是一個菜單導航,它的圖標包含了6張圖片(選中和非選中態(tài)使用不同圖片),一個合理的方式是,使用雪碧圖(Sprite),將多個連續(xù)的圖片合并為一張,通過改變背景圖位置來控制圖片顯示。

2.3 優(yōu)化用戶感知
2.3.1 滾動加載
微應用模塊的展示形式為櫥窗卡片組成的長列表(10個左右,后續(xù)需求迭代還會增加)。櫥窗卡片作為基礎的布局組件,承接了篩選區(qū)域、下鉆功能、可視化組件的展示功能,如果在首屏一次加載太多,將會導致較大的渲染壓力。

滾動加載是提升首屏渲染性能的很好方案,它的核心是判斷目標元素(櫥窗卡片)是否在可視區(qū)域范圍內,當其不可見時,只需在卡片內容區(qū)渲染一個有默認高度的占位元素,同時也不必加載篩選區(qū)域組件,當用戶滾動到該區(qū)域時,再渲染業(yè)務相關的內容。

如圖所示,首屏只需渲染核心指標和目標監(jiān)控兩個櫥窗卡片的內容,其余卡片只渲染一個占位元素,有效緩解瀏覽器的渲染壓力。那么具體邏輯是怎么實現(xiàn)的呢?
通常有兩種方式可判斷元素是否在可視范圍內:
1、監(jiān)聽scroll事件,獲取目標元素(櫥窗卡片)相對于視口的坐標,再判斷其是否在可視區(qū)域內。該方法的缺點是,scroll事件密集觸發(fā),容易造成性能問題。
2、IntersectionObserver API,翻譯為”交叉觀察器“,可以自動"觀察"元素是否可見,Chrome 51+ 支持。
這里我們采用第二種方案,初始狀態(tài)設置內容區(qū)域不可見,當元素掛載完成后,創(chuàng)建io監(jiān)聽器,監(jiān)聽到卡片位于可視范圍內,再加載對應組件,具體邏輯參考以下代碼。
function WindowCard ({ title, filter, children }) {
const wcRef = useRef(null);
// 初始狀態(tài),默認設置內容區(qū)域不可見
const [visible, setVisible] = useState(!useObserver);
useEffect(() {
// 元素掛載后,創(chuàng)建io監(jiān)聽器
const io {
if (entries[0].intersectionRatio 0) return;
// 當元素位于可視區(qū)域范圍內后,加載業(yè)務組件
setVisible(true);
// 業(yè)務組件加載后,斷開監(jiān)聽
io.unobserve(wcRef.current);
});
io.observe(wcRef.current);
return () {
io.disconnect();
};
}, []);
return (
='window-card' ref={wcRef}>
// 省略head部分邏輯
='window-card-content'>
{visible ={{ height}
/div>
/div>
);
}
2.3.2 優(yōu)化CLS指標
根據(jù)LightHouse統(tǒng)計數(shù)據(jù),CLS指標得分值很低,其反映了在首屏頁面加載時,出現(xiàn)了較大的布局偏移。
類似于核心指標卡片,在指標卡加載前,內容區(qū)域高度為0,而loading加載后,有一個200px的占位高度,真實的指標卡渲染出來后,高度變?yōu)?22px,用戶能夠感覺到明顯的抖動。

還有指標詳情頁中的場景,在指標卡loading階段,占位高度為200px,而真實渲染的指標卡的高度為142px。假如用戶在此時點擊某個可點擊區(qū)域,很容以造成誤觸,帶來很差的用戶體驗。

諸如以上的場景,我們事先能夠確定指標卡渲染出來的真實高度,可以提前將其固定。參照類似的方法,檢查其他的頁面,能夠盡量避免頁面首屏的抖動。
3、實戰(zhàn)總結
通過抽取公共依賴和搖樹優(yōu)化,微應用的體積從5000多k減小到2000k以內,應用體驗值從58分提升到65分。通過對長列表頁面適配滾動加載,提升了頁面的渲染效率;固定首屏指標卡等元素的高度,減少了頁面布局偏移,從而保證頁面穩(wěn)定的顯示效果;將用戶高頻使用頁面(指標詳情頁)獨立為微應用,直接將拖后腿的頁面變成性能表現(xiàn)優(yōu)秀的頁面;資源預加載,充分利用了瀏覽器的空余資源,有效提高頁面加載速度。應用的體驗值,從65分提升到75分:
優(yōu)化前:

優(yōu)化后:

細分指標的表現(xiàn)都有了明顯提升,以下為優(yōu)化前后的對比數(shù)據(jù):

本次優(yōu)化的重點是提升商智C店首屏顯示的效果,目標是將燭龍統(tǒng)計得分提升到體驗良好的水平(75分及以上),基本完成了預定目標。因優(yōu)化過程位于需求迭代的間隙,并不計劃對系統(tǒng)架構及業(yè)務代碼進行深度調整,因此還有些遺留工作放到了后續(xù):如微應用的體積仍有進一步優(yōu)化的空間、TBT指標得分雖有提升,但表現(xiàn)仍較差、某些組件交互動效卡頓等。
最后,期望本文的經(jīng)驗能給同樣在做性能優(yōu)化的小伙伴一些參考,文章的不足之處,可在評論區(qū)討論。敬請期待后續(xù)移動端交互優(yōu)化的文章。
作者:京東零售 郄鵬飛
來源:京東云開發(fā)者社區(qū) 轉載請注明來源
浙公網(wǎng)安備 33010602011771號