摘要:我們的目標(biāo)是讓的頁(yè)面也能夠擁有般的體驗(yàn),如果你還在尋求什么技術(shù)能夠讓老板虎軀一震拯救你的,那么這篇文章或許能夠幫助到你。這是一個(gè)使用編寫(xiě)的頁(yè)面,運(yùn)行于多端,包括企鵝輔導(dǎo)手機(jī)手機(jī)瀏覽器。經(jīng)過(guò)我們的測(cè)試發(fā)現(xiàn)安卓基本上都是支持的,需要以上才支持。
本文由云+社區(qū)發(fā)表
作者:思衍Jax
天下武功,唯 (wei) 快(fu) 不(bu) 破(po)。
隨著近幾年的前端技術(shù)的高速發(fā)展,越來(lái)越多的團(tuán)隊(duì)使用 React、Vue 等 SPA 框架作為其主要的技術(shù)棧。以 React 應(yīng)用為例,從性能角度,其最重要的指標(biāo)可能就是首屏渲染所花費(fèi)的時(shí)間了。那么今天,我們要給大家分享的一個(gè)把優(yōu)化做到極致的故事。
我們的目標(biāo)是讓 H5 的頁(yè)面也能夠擁有 Native 般的體驗(yàn),如果你還在尋求什么技術(shù)能夠讓老板虎軀一震(拯救你的KPI),那么這篇文章或許能夠幫助到你。
企鵝輔導(dǎo)詳情頁(yè)
課程詳情頁(yè)是騰訊旗下企鵝輔導(dǎo) APP 中最重要頁(yè)面之一,也是流量最大的頁(yè)面之一,所以它的打開(kāi)速度也是至關(guān)重要的。
這是一個(gè)使用 React
編寫(xiě)的 H5 頁(yè)面,運(yùn)行于多端,包括: 企鵝輔導(dǎo)APP
、手機(jī) QQ
、手機(jī)瀏覽器
。
我們知道當(dāng)前主流的 SPA 的應(yīng)用的默認(rèn)渲染方式都是這樣的:
在這種情況下,從加載頁(yè)面到用戶(hù)看到頁(yè)面(首屏渲染所花費(fèi)的時(shí)間)就是上圖中灰色邊框區(qū)域所包括的時(shí)間。
這是最慢的一種方式,就算 CGI 夠快,最少要花費(fèi) 1S 到 2S 左右的時(shí)間了。
接著我們簡(jiǎn)單優(yōu)化一下:
怎么做到呢?我們的方案是統(tǒng)一封裝 Request 請(qǐng)求工具,在用 Webpack 打包的時(shí)候,會(huì)往頁(yè)面頂部注入一段 預(yù)加載 CGI 的 JS 代碼,維護(hù)一個(gè)CGI 與 DATA 對(duì)應(yīng) MAP,后面發(fā)請(qǐng)求的時(shí)候,先去 MAP 里取值,如果有值的話直接拿出來(lái),沒(méi)有的話則發(fā)起HTTP 請(qǐng)求。(具體請(qǐng)查閱我們團(tuán)隊(duì)開(kāi)源的 Preload 工具)
這種模式還有一些其他的優(yōu)化的方法:
效果如下圖所示:
異步渲染
在異步的模式下,除了上述優(yōu)化,我們還在端內(nèi)(企鵝輔導(dǎo) APP、手機(jī) QQ)內(nèi)做了離線包緩存(騰訊手Q方面獨(dú)立研發(fā)出來(lái)的針對(duì)手機(jī)端優(yōu)化的方案,簡(jiǎn)而言之就是將靜態(tài)資源緩存在手機(jī) APP 內(nèi)),經(jīng)過(guò)我們的數(shù)據(jù)測(cè)試,首屏渲染大概能夠達(dá)到秒開(kāi)(1s左右) 的效果。
-w300
但對(duì)有著性能極致追求的我們來(lái)說(shuō),肯定是不會(huì)滿(mǎn)意的。
繼續(xù)優(yōu)化,最容易、最大眾的套路肯定就是直出(服務(wù)端渲染)了。
現(xiàn)在直出的方案已經(jīng)有很多很多種,這里也不多做介紹了,如果您想了解更多關(guān)于服務(wù)端渲染的方案,請(qǐng)參考這篇文章。
直出針對(duì)首屏?xí)r間的優(yōu)化效果是非常明顯的,經(jīng)過(guò)我們的測(cè)試,數(shù)據(jù)大概能夠提升25%左右。
直出之后的效果如下圖:
直出同構(gòu)
可以看到對(duì)于首屏來(lái)說(shuō),沒(méi)有了【加載中...】的等待時(shí)間,視覺(jué)體驗(yàn)提升了不少。
PWA
針對(duì)上述、常見(jiàn)的直出應(yīng)用來(lái)說(shuō),我們能夠優(yōu)化的點(diǎn)在哪里呢?讓我們來(lái)詳細(xì)分析一波,這也是今天我們要給大家分享的重點(diǎn)。
首先看看直出應(yīng)用各個(gè)環(huán)節(jié)的耗時(shí)表 (本地環(huán)境 2018款 iMac):
過(guò)程名稱(chēng) | 過(guò)程花費(fèi) |
---|---|
Node 內(nèi) CGI 拉取 | 300 ms |
RenderToString | 20 ms |
網(wǎng)絡(luò)耗時(shí) | 10 ms |
前端HTML渲染 | 30 ms |
從上面的表中我們看出,直出渲染的耗時(shí)的大頭還是在 CGI 接口的拉取上。
我們現(xiàn)在提出兩個(gè)問(wèn)題:
動(dòng)態(tài)信息
這個(gè)頁(yè)面的接口數(shù)據(jù)中,有一些數(shù)據(jù),是實(shí)時(shí)變動(dòng)的, 比如:當(dāng)前還剩多少個(gè)名額、此時(shí)此刻課程的價(jià)格、用戶(hù)是否購(gòu)買(mǎi)過(guò)這個(gè)課程等。
這些數(shù)據(jù)的特性決定了這個(gè)數(shù)據(jù)接口不能夠被緩存。(假設(shè)將其緩存,那么就會(huì)存在可能用戶(hù)進(jìn)來(lái)看到當(dāng)前還剩下10個(gè)名額,其實(shí)課程已經(jīng)賣(mài)光了的情況)
為了這個(gè)時(shí)間耗時(shí)的大頭,我們做了CGI接口的動(dòng)靜分離。
將與用戶(hù)態(tài)、當(dāng)前時(shí)間沒(méi)有關(guān)聯(lián)的數(shù)據(jù)(比如
課程標(biāo)題
、課程上課的時(shí)間
、試聽(tīng)模塊的地址
等)放在一個(gè)接口(靜態(tài)接口),其他變化的數(shù)據(jù)放在另一個(gè)接口(動(dòng)態(tài)接口)。
那么可以使用靜態(tài)的接口來(lái)做服務(wù)端渲染,好處是第一比較快(少了動(dòng)態(tài)的信息,而且后臺(tái)也可以做緩存),第二 Node 直出可以做緩存了。
這樣我們就可以將那部分靜態(tài)的、不會(huì)經(jīng)常變動(dòng)的數(shù)據(jù)用來(lái)直出 HTML,然后將這個(gè) HTML 文件緩存到 Redis 中。
客戶(hù)端請(qǐng)求此網(wǎng)頁(yè),Node 端接受到請(qǐng)求之后,先去 Redis 里拿緩存的 HTML,如果 Redis 緩存沒(méi)有命中,則拉取靜態(tài)的 CGI 接口渲染出 HTML存入 Redis。
客戶(hù)端拿到 HTML 之后,會(huì)立刻渲染,然后再用 JS 去請(qǐng)求動(dòng)態(tài)的數(shù)據(jù),渲染到相應(yīng)的地方。
做完之后我們可以看到優(yōu)化效果的提升是非常非常明顯的:
直接從 262ms 提升到了 16ms !(本地環(huán)境),簡(jiǎn)直飛一般的感覺(jué),媽媽再也不用擔(dān)心領(lǐng)導(dǎo)看耗時(shí)了。
關(guān)于什么是 PWA ,以及如何使用,請(qǐng)移步這篇文章。
做了 Node 端直出的 HTML 緩存之后,我們接著優(yōu)化,接著思考,是否可以在客戶(hù)端也緩存 HTML,這樣連網(wǎng)絡(luò)延時(shí)這部分消耗也省掉呢。
答案就是使用 PWA 在客戶(hù)端做離線緩存,將我們直出的 HTML 緩存在客戶(hù)端,每次用戶(hù)請(qǐng)求的時(shí)候,直接從 PWA 離線緩存里取出對(duì)應(yīng)的直出頁(yè)面(HTML)響應(yīng)給用戶(hù),響應(yīng)之后緊接著請(qǐng)求 Node 服務(wù)更新本地的 PWA 緩存。(如下圖所示)
核心代碼:
self.addEventListener("fetch", event => {
// TODO other logic (maybe fetch filter)
// core logic
event.respondWith(
caches.open(cacheName).then(function(cache) {
return cache.match(cacheCourseUrl).then(function(response) {
var fetchPromise = fetch(cacheCourseUrl).then(function(
networkResponse
) {
if (networkResponse.status === 200) {
cache.put(cacheCourseUrl, networkResponse.clone());
}
return networkResponse;
});
return response || fetchPromise;
});
})
);
});
廢話不多說(shuō),先看效果對(duì)比 (左 PWA 直出;右 離線包):
duibi
從上圖可以看出,使用了 PWA 直出緩存之后,首屏渲染基本是毫秒開(kāi),可以說(shuō)與 Native 并肩了。
經(jīng)過(guò)我們的數(shù)據(jù)測(cè)試,使用 PWA 直出緩存,首屏渲染的時(shí)間最好可以到400ms左右級(jí)別:
因?yàn)閷?duì)接口進(jìn)行了動(dòng)靜分離,使用靜態(tài)接口直出頁(yè)面,然后在客戶(hù)端拉取動(dòng)態(tài)數(shù)據(jù)渲染完。這就可能會(huì)導(dǎo)致頁(yè)面的抖動(dòng)(比如詳情頁(yè)中的試聽(tīng)模塊,是在客戶(hù)端渲染的)。
因?yàn)楦叨雀淖兞耍曈X(jué)上就會(huì)出現(xiàn)抖動(dòng)(具體可以參考上面章節(jié)直出時(shí)候的 GIF 截圖)。
要去掉頁(yè)面抖動(dòng)的情況,就必須保證容器的高度在直出時(shí)候已經(jīng)存在了。
比如這個(gè)試聽(tīng)模塊,其實(shí)這個(gè)封面圖和試聽(tīng)按鈕是可以在服務(wù)端渲染出來(lái)的,而后面的 Video 模塊則必須要在客戶(hù)度渲染(騰訊云 Tcplayer)。
所以這里可以拆分成:(試聽(tīng)封面 + 按鈕 + 時(shí)間)服務(wù)端渲染 + 底層 Video(客戶(hù)端渲染)。
有些需要在客戶(hù)端計(jì)算高度的容器(表現(xiàn)為常放在 ComponentDidMount 里計(jì)算),如果它們依賴(lài)客戶(hù)端環(huán)境(比如依賴(lài)當(dāng)前系統(tǒng)是安卓還是 IOS),就導(dǎo)致他們肯定不能放在服務(wù)端直接渲染出來(lái),這又怎么辦呢?
這里我們的做法,是將這些計(jì)算放在 HTML body 之前,通過(guò)內(nèi)聯(lián)的腳本嵌入,計(jì)算出當(dāng)前環(huán)境,給 body 加上一個(gè)特定的類(lèi)(class),然后在這個(gè)特定的類(lèi)下面的元素,就可以通過(guò) css 給予特定的樣式。比如下面代碼:
/*
* 因?yàn)樵诓煌氖謾C(jī) APP 環(huán)境內(nèi),頁(yè)面的 padding 是不一樣的。
* 我們要在頁(yè)面渲染完之前加上相應(yīng)的 padding
*/
var REGEXP_FUDAO_APP = /EducationApp/;
if (
typeof navigator !== "undefined" &&
REGEXP_FUDAO_APP.test(navigator.userAgent)
) {
if (/Android/i.test(navigator.userAgent)) {
document.body.classList.add("androidFudaoApp");
} else if (/iPhone|iPad|iPod|iOS/i.test(navigator.userAgent)) {
if (window.screen.width === 375 && window.screen.height === 812) {
document.body.classList.add("iphoneXFudaoApp");
} else {
document.body.classList.add("iosFudaoApp");
}
}
}
.androidFudaoApp .tt {
padding-top: 48px;
background-position-y: 84px;
}
.iphoneXFudaoApp .tt {
padding-top: 88px;
background-position-y: 124px;
}
.iosFudaoApp .tt {
padding-top: 64px;
background-position-y: 100px;
}
然后把這段代碼通過(guò)構(gòu)建插入到頁(yè)面 body 之前。
-w500
防抖動(dòng)優(yōu)化效果如下 (左優(yōu)化完,右未優(yōu)化):
duibi_doudong
雖然我們做了 PWA 離線緩存,但是對(duì)于冷啟動(dòng)來(lái)說(shuō),客戶(hù)端里面的 PWA 緩存還是沒(méi)有的,這樣就會(huì)導(dǎo)致初次點(diǎn)擊頁(yè)面,渲染速度相對(duì)慢一點(diǎn)。
這里我們可以在 APP 啟動(dòng)的時(shí)候,用一個(gè)預(yù)加載的腳本最大限度的拉取用戶(hù)可能訪問(wèn)的頁(yè)面。
核心代碼如下:
// 預(yù)加載頁(yè)面時(shí), PWA 預(yù)緩存課程詳情頁(yè)面的直出
function prefetchCache(fetchUrl) {
fetch("https://you preFetch Cgi")
.then(data => {
return data.json();
})
.then(res => {
const { courseInfo = [] } = res.result || {};
courseInfo.forEach(item => {
if (item.cid) {
caches.open(cacheName).then(function(cache) {
fetch(`${courseURL}?course_id=${item.cid}`).then(function(
networkResponse
) {
if (networkResponse.status === 200) {
cache.put(
`${courseURL}?course_id=${item.cid}`,
networkResponse.clone()
);
}
// return networkResponse;
});
});
}
});
})
.catch(err => {
// To monitor err
});
}
隨著 PWA 技術(shù)的發(fā)展,現(xiàn)今大部分手機(jī)以及 PC 環(huán)境已經(jīng)支持對(duì) PWA 進(jìn)行了支持。經(jīng)過(guò)我們的測(cè)試發(fā)現(xiàn):安卓基本上都是支持的,IOS 需要11.3以上才支持。
Service Workers 兼容性圖
很多的經(jīng)驗(yàn)告訴我們,外聯(lián)的 script 標(biāo)簽要放在 body 的后面,因?yàn)樗鼤?huì)阻塞頁(yè)面的 DOM 渲染。
經(jīng)過(guò)測(cè)試發(fā)現(xiàn),IOS 的 WebView
(UIWebView
)渲染機(jī)制并不會(huì)上述一樣,而是要等到后面的 JS 執(zhí)行完之后才渲染頁(yè)面,如果是這樣,我們的直出渲染優(yōu)化就沒(méi)有效果了(因?yàn)?HTML 并不在最開(kāi)始渲染),這里可以使用 script
標(biāo)簽的 async
與 defer
屬性來(lái)達(dá)到異步渲染的作用。
升級(jí) WkWebView 之后,情況得到改善,渲染正常。
此文已由作者授權(quán)騰訊云+社區(qū)在各渠道發(fā)布
原文:企鵝輔導(dǎo)課程詳情頁(yè)毫秒開(kāi)的秘密 - PWA 直出
獲取更多新鮮技術(shù)干貨,可以關(guān)注我們騰訊云技術(shù)社區(qū)-云加社區(qū)官方號(hào)及知乎機(jī)構(gòu)號(hào)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/1301.html
摘要:我們的目標(biāo)是讓的頁(yè)面也能夠擁有般的體驗(yàn),如果你還在尋求什么技術(shù)能夠讓老板虎軀一震拯救你的,那么這篇文章或許能夠幫助到你。這是一個(gè)使用編寫(xiě)的頁(yè)面,運(yùn)行于多端,包括企鵝輔導(dǎo)手機(jī)手機(jī)瀏覽器。經(jīng)過(guò)我們的測(cè)試發(fā)現(xiàn)安卓基本上都是支持的,需要以上才支持。 本文由云+社區(qū)發(fā)表作者:思衍Jax showImg(https://segmentfault.com/img/remote/1460000017...
摘要:優(yōu)化的探索與最佳實(shí)踐讓老板虎軀一震的前端技術(shù),殺手直出是個(gè)什么概念基于框架在中型移動(dòng)端項(xiàng)目的直出同構(gòu)實(shí)踐瀏覽器瀏覽器渲染原理及流程瀏覽器渲染原理及解剖瀏覽器內(nèi)部工作原理全新使用指南開(kāi)發(fā)者工具之內(nèi)存分析網(wǎng)站加載時(shí)間過(guò)長(zhǎng)的原因和解決辦法深度剖析 優(yōu)化 PWA 的探索與最佳實(shí)踐 讓老板虎軀一震的前端技術(shù),KPI殺手 直出 是個(gè)什么概念? 基于 TVUE 框架在中型移動(dòng)端項(xiàng)目的直出同構(gòu)實(shí)踐 ...
閱讀 1123·2023-04-26 00:12
閱讀 3249·2021-11-17 09:33
閱讀 1061·2021-09-04 16:45
閱讀 1186·2021-09-02 15:40
閱讀 2145·2019-08-30 15:56
閱讀 2950·2019-08-30 15:53
閱讀 3548·2019-08-30 11:23
閱讀 1932·2019-08-29 13:54