摘要:我們的目標是讓的頁面也能夠擁有般的體驗,如果你還在尋求什么技術能夠讓老板虎軀一震拯救你的,那么這篇文章或許能夠幫助到你。這是一個使用編寫的頁面,運行于多端,包括企鵝輔導手機手機瀏覽器。經過我們的測試發現安卓基本上都是支持的,需要以上才支持。
本文由云+社區發表作者:思衍Jax
天下武功,唯 (wei) 快(fu) 不(bu) 破(po)。
隨著近幾年的前端技術的高速發展,越來越多的團隊使用 React、Vue 等 SPA 框架作為其主要的技術棧。以 React 應用為例,從性能角度,其最重要的指標可能就是首屏渲染所花費的時間了。那么今天,我們要給大家分享的一個把優化做到極致的故事。
我們的目標是讓 H5 的頁面也能夠擁有 Native 般的體驗,如果你還在尋求什么技術能夠讓老板虎軀一震(拯救你的KPI),那么這篇文章或許能夠幫助到你。
企鵝輔導課程詳情頁是什么企鵝輔導詳情頁
課程詳情頁是騰訊旗下企鵝輔導 APP 中最重要頁面之一,也是流量最大的頁面之一,所以它的打開速度也是至關重要的。
這是一個使用 React 編寫的 H5 頁面,運行于多端,包括: 企鵝輔導APP、手機 QQ、手機瀏覽器。
架構演變我們知道當前主流的 SPA 的應用的默認渲染方式都是這樣的:
在這種情況下,從加載頁面到用戶看到頁面(首屏渲染所花費的時間)就是上圖中灰色邊框區域所包括的時間。
這是最慢的一種方式,就算 CGI 夠快,最少要花費 1S 到 2S 左右的時間了。
接著我們簡單優化一下:
把靜態資源緩存起來,這樣下次用戶打開的時候就不用從網絡請求了。
第 ④ 步拉取 CGI 這個動作是否可以提前呢?我們可以在請求 HTML 之后,先通過一段 JS 腳本去請求 CGI 數據,后面第 ④ 步的時候,就可以直接拿到數據了,這就是 CGI 預加載。
怎么做到呢?我們的方案是統一封裝 Request 請求工具,在用 Webpack 打包的時候,會往頁面頂部注入一段 預加載 CGI 的 JS 代碼,維護一個CGI 與 DATA 對應 MAP,后面發請求的時候,先去 MAP 里取值,如果有值的話直接拿出來,沒有的話則發起HTTP 請求。(具體請查閱我們團隊開源的 Preload 工具)
這種模式還有一些其他的優化的方法:
在 HTML 內實現 Loading 態或者骨架屏;
去掉外聯 css;
使用動態 polyfill;
使用 SplitChunksPlugin 拆分公共代碼;
正確地使用 Webpack 4.0 的 Tree Shaking;
使用動態 import,切分頁面代碼,減小首屏 JS 體積;
編譯到 ES2015+,提高代碼運行效率,減小體積;
使用 lazyload 和 placeholder 提升加載體驗。
效果如下圖所示:
異步渲染
在異步的模式下,除了上述優化,我們還在端內(企鵝輔導 APP、手機 QQ)內做了離線包緩存(騰訊手Q方面獨立研發出來的針對手機端優化的方案,簡而言之就是將靜態資源緩存在手機 APP 內),經過我們的數據測試,首屏渲染大概能夠達到秒開(1s左右) 的效果。
-w300
但對有著性能極致追求的我們來說,肯定是不會滿意的。
繼續優化,最容易、最大眾的套路肯定就是直出(服務端渲染)了。
現在直出的方案已經有很多很多種,這里也不多做介紹了,如果您想了解更多關于服務端渲染的方案,請參考這篇文章。
直出針對首屏時間的優化效果是非常明顯的,經過我們的測試,數據大概能夠提升25%左右。
直出之后的效果如下圖:
直出同構
可以看到對于首屏來說,沒有了【加載中...】的等待時間,視覺體驗提升了不少。
PWA 直出PWA
針對上述、常見的直出應用來說,我們能夠優化的點在哪里呢?讓我們來詳細分析一波,這也是今天我們要給大家分享的重點。
首先看看直出應用各個環節的耗時表 (本地環境 2018款 iMac):
過程名稱 | 過程花費 |
---|---|
Node 內 CGI 拉取 | 300 ms |
RenderToString | 20 ms |
網絡耗時 | 10 ms |
前端HTML渲染 | 30 ms |
從上面的表中我們看出,直出渲染的耗時的大頭還是在 CGI 接口的拉取上。
我們現在提出兩個問題:
CGI 接口的數據是否可以緩存 ?
HTML 又是否可以緩存 ?
動態信息
這個頁面的接口數據中,有一些數據,是實時變動的, 比如:當前還剩多少個名額、此時此刻課程的價格、用戶是否購買過這個課程等。
這些數據的特性決定了這個數據接口不能夠被緩存。(假設將其緩存,那么就會存在可能用戶進來看到當前還剩下10個名額,其實課程已經賣光了的情況)
為了這個時間耗時的大頭,我們做了CGI接口的動靜分離。
將與用戶態、當前時間沒有關聯的數據(比如課程標題、課程上課的時間、試聽模塊的地址等)放在一個接口(靜態接口),其他變化的數據放在另一個接口(動態接口)。
那么可以使用靜態的接口來做服務端渲染,好處是第一比較快(少了動態的信息,而且后臺也可以做緩存),第二 Node 直出可以做緩存了。
這樣我們就可以將那部分靜態的、不會經常變動的數據用來直出 HTML,然后將這個 HTML 文件緩存到 Redis 中。
客戶端請求此網頁,Node 端接受到請求之后,先去 Redis 里拿緩存的 HTML,如果 Redis 緩存沒有命中,則拉取靜態的 CGI 接口渲染出 HTML存入 Redis。
客戶端拿到 HTML 之后,會立刻渲染,然后再用 JS 去請求動態的數據,渲染到相應的地方。
做完之后我們可以看到優化效果的提升是非常非常明顯的:
直接從 262ms 提升到了 16ms !(本地環境),簡直飛一般的感覺,媽媽再也不用擔心領導看耗時了。
關于什么是 PWA ,以及如何使用,請移步這篇文章。
做了 Node 端直出的 HTML 緩存之后,我們接著優化,接著思考,是否可以在客戶端也緩存 HTML,這樣連網絡延時這部分消耗也省掉呢。
答案就是使用 PWA 在客戶端做離線緩存,將我們直出的 HTML 緩存在客戶端,每次用戶請求的時候,直接從 PWA 離線緩存里取出對應的直出頁面(HTML)響應給用戶,響應之后緊接著請求 Node 服務更新本地的 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; }); }) ); });
廢話不多說,先看效果對比 (左 PWA 直出;右 離線包):
duibi
從上圖可以看出,使用了 PWA 直出緩存之后,首屏渲染基本是毫秒開,可以說與 Native 并肩了。
經過我們的數據測試,使用 PWA 直出緩存,首屏渲染的時間最好可以到400ms左右級別:
PWA 直出細節優化因為對接口進行了動靜分離,使用靜態接口直出頁面,然后在客戶端拉取動態數據渲染完。這就可能會導致頁面的抖動(比如詳情頁中的試聽模塊,是在客戶端渲染的)。
因為高度改變了,視覺上就會出現抖動(具體可以參考上面章節直出時候的 GIF 截圖)。
要去掉頁面抖動的情況,就必須保證容器的高度在直出時候已經存在了。
比如這個試聽模塊,其實這個封面圖和試聽按鈕是可以在服務端渲染出來的,而后面的 Video 模塊則必須要在客戶度渲染(騰訊云 Tcplayer)。
所以這里可以拆分成:(試聽封面 + 按鈕 + 時間)服務端渲染 + 底層 Video(客戶端渲染)。
有些需要在客戶端計算高度的容器(表現為常放在 ComponentDidMount 里計算),如果它們依賴客戶端環境(比如依賴當前系統是安卓還是 IOS),就導致他們肯定不能放在服務端直接渲染出來,這又怎么辦呢?
這里我們的做法,是將這些計算放在 HTML body 之前,通過內聯的腳本嵌入,計算出當前環境,給 body 加上一個特定的類(class),然后在這個特定的類下面的元素,就可以通過 css 給予特定的樣式。比如下面代碼:
/* * 因為在不同的手機 APP 環境內,頁面的 padding 是不一樣的。 * 我們要在頁面渲染完之前加上相應的 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; }
然后把這段代碼通過構建插入到頁面 body 之前。
-w500
防抖動優化效果如下 (左優化完,右未優化):
duibi_doudong
雖然我們做了 PWA 離線緩存,但是對于冷啟動來說,客戶端里面的 PWA 緩存還是沒有的,這樣就會導致初次點擊頁面,渲染速度相對慢一點。
這里我們可以在 APP 啟動的時候,用一個預加載的腳本最大限度的拉取用戶可能訪問的頁面。
核心代碼如下:
// 預加載頁面時, PWA 預緩存課程詳情頁面的直出 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 直出遺留問題
隨著 PWA 技術的發展,現今大部分手機以及 PC 環境已經支持對 PWA 進行了支持。經過我們的測試發現:安卓基本上都是支持的,IOS 需要11.3以上才支持。
Service Workers 兼容性圖
很多的經驗告訴我們,外聯的 script 標簽要放在 body 的后面,因為它會阻塞頁面的 DOM 渲染。
經過測試發現,IOS 的 WebView (UIWebView)渲染機制并不會上述一樣,而是要等到后面的 JS 執行完之后才渲染頁面,如果是這樣,我們的直出渲染優化就沒有效果了(因為 HTML 并不在最開始渲染),這里可以使用 script 標簽的 async 與 defer 屬性來達到異步渲染的作用。
升級 WkWebView 之后,情況得到改善,渲染正常。
附錄PWA 的探索與最佳實踐
億萬級訪問量下的前端同構直出實踐
React 16 加載性能優化指南
此文已由作者授權騰訊云+社區在各渠道發布
原文:企鵝輔導課程詳情頁毫秒開的秘密 - PWA 直出
獲取更多新鮮技術干貨,可以關注我們騰訊云技術社區-云加社區官方號及知乎機構號
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/53567.html
摘要:我們的目標是讓的頁面也能夠擁有般的體驗,如果你還在尋求什么技術能夠讓老板虎軀一震拯救你的,那么這篇文章或許能夠幫助到你。這是一個使用編寫的頁面,運行于多端,包括企鵝輔導手機手機瀏覽器。經過我們的測試發現安卓基本上都是支持的,需要以上才支持。 本文由云+社區發表 作者:思衍Jax 天下武功,唯 (wei) 快(fu) 不(bu) 破(po)。 隨著近幾年的前端技術的高速發展,越來越多的...
閱讀 3154·2021-11-22 14:45
閱讀 3300·2019-08-29 13:11
閱讀 2306·2019-08-29 12:31
閱讀 922·2019-08-29 11:21
閱讀 2991·2019-08-29 11:09
閱讀 3616·2019-08-28 18:11
閱讀 1420·2019-08-26 13:58
閱讀 1273·2019-08-26 13:27