導讀:從粗糙到精緻,從簡單到複雜,全球互聯網Web App(網頁應用)平均體積已增壓到1.6Mb,隨着音視頻等富媒體內容的流量池膨脹,終端設備上的用戶對網頁裝載速度尤爲敏感。頁面不能作到秒開,就會有大量用戶選擇離開。重視並改善網站性能,優化即時網頁裝載時間,加速即時網頁在瀏覽器平臺終端狀態展示,進而能夠帶來網站流量增加。
本文源自百度直播研發部,提出了前端漸進加強的頁面渲染方案,即「路由分離+預靜態化+WebView預建立」方案,來替代模板同步渲染方案,並採用工程化的方式將能力打包下沉賦能產品線。html
在直播業務落地的場景中,構建打賞服務精神,創造風格多樣,使人難忘的運營活動形式,以知足目標用戶需求。進行有效的服務創新,保障H5服務品質,控制訪問性能,及時化解金主情緒爲商業價值,製造特效驚喜,流暢的用戶體驗,有利於贏得用戶對平臺的尊重,提高平臺品牌氣質:前端
1. 優化訪問體驗:優化H5首屏性能,圍繞提高用戶服務感覺和提高開發者工做效率進行創新,探索Web追趕原生的體驗創新;
2. 加速需求閉環:解決H5頁面路由與數據接口耦合的問題,實現H5服務自治,釋放業務對Web產品形態的旺盛需求;
3. 基礎設施建設:使用工程化手段沉澱公共通用的前端基礎設施,讓前端組織更敏捷的支持跨業務的人力調配,技術賦能更多產品線;react
圍繞以用戶爲中心的功能完善的體驗,以客戶爲中心的商業價值的轉化。提高H5服務品質,發揮現代瀏覽器潛能,賦能傳統Web新的解決方案。
歷史架構的緣由,H5頁面的加載過程是Server向Smarty模板注入json_encode後的接口主數據,響應給瀏覽器帶有頁面徹底JSON數據的頁面,而後在瀏覽器端執行JavaScript,最終Paint給用戶,流程以下圖:webpack
△H5優化前的加載與渲染流程
△H5首屏關鍵路徑耗時拆解git
兩個影響H5首屏內容(FCP,First Contentful Paint,首次內容繪製)的關鍵路徑爲:
1. 網絡耗時:依賴Server端數據查詢及模板編譯,當數據查詢慢時延遲了首字節到達(TTFB,Time To First Byte,首字節時間)。
2. 內核渲染耗時:依賴JavaScript執行讀取頁面主數據並生成徹底的DOM結構。
所以,咱們針對性地設計並實施了「路由分離+預靜態化+WebView預建立」的方案,改進後的頁面加載與渲染流程以下圖:github
△H5優化後的加載與渲染流程
web
路由分離以前H5頁面的URL由Server分配,前端負責編寫TPL模板產物,TPL與最終URL的對應關係在Server經過配置文件作映射,日益暴露出啓動開發慢,頁面後期維護交接溝通成本高的問題。 因此咱們但願頁面路由規範化,讓前端開發者自主控制頁面入口格式,讓後端開發者更專一於API接口數據邏輯。所以,咱們設計了前端路由分離方案,約定了頁面URL與頁面源代碼目錄映射關係,規則以下圖:△預約式URL路由規範
NGINX直接響應預靜態化的HTML文件,首字節到達不依賴數據查詢與模版編譯。
json
前端路由分離直接在NGINX代理層返回HTML文件,但沒有頁面徹底渲染須要的數據,在執行AJAX請求沒有返回以前,須要規避頁面一直處於白屏或全局loading狀態,提早FCP的時間。 咱們採用了預靜態化頁面的方案。預靜態化不像服務器渲染那樣即時編譯產出徹底靜態化的 HTML,它只在構建時爲了特定的路由生成特定的幾個靜態頁面,咱們能夠經過 webpack插件將一些特定頁面在編譯時就注入DOM結構,這樣作有幾個好處:第一縮短頁面白屏時間,第二相對於服務器端渲染節省雲基礎設施資源成本,第三輸出給搜索引擎爬取頁面通用內容。 結合實際應用場景和市面上主流的預靜態化方式,最終咱們開發了基於ReactDomServer原生的服務端渲染能力的webpack插件,提高預靜態化性能和效率。 經過webpack插件系統獲取每次構建的compilation上下文,經過html-webpack-plugin的before AssetTagGeneration hook獲取當前頁面bundle,afterTemplateExecution hook獲取當前頁面編譯後的模板HTML,經過eval執行bundle導出的整個頁面APP模塊,經過ReactDomServer對單頁應用的每一個路由產出APP HTML與模板HTML合併後落盤爲預靜態化的HTML。小程序
H5預靜態化調用序列以下:後端
△H5預靜態化調用序列
頁面入口export APP:
module.exports = (locals) => Promise.resolve(locals.preRender({id: containerId, main: App}));
基於Node.js環境,建立JSDOM實例提供瀏覽器宿主環境,使用ReactDOMServer將組件渲染爲靜態HTML標記。使用自研的預靜態化webpack插件替換html-webpack-plugin,並不增長新的編譯階段。 Webpack插件與市面上主流的預靜態化方法作個對比,如基於無頭瀏覽器的、基於本地啓動HTTP Server、通用的靜態站點生成工具等。結果以下:
react-snap |
prerender-spa-plugin |
react-snapshot |
prep |
snapshotify |
@baidu/html-webpack-pre-render-plugin |
|
State |
supported |
supported |
unsupported |
unsupported |
experimental |
supported |
DOM implementation |
puppeteer |
phantomjs-prebuilt |
jsdom |
nightmare |
puppeteer |
jsdom+implement |
Load performance optimisation |
+ |
- |
- |
- |
+ |
- |
Zero-configuration |
+ |
- |
+ |
- |
+ |
+ |
Redux |
+ |
- |
+ |
- |
- |
+ |
Async components |
+ |
- |
- |
- |
+ |
- |
Webpack code splitting |
+ |
+ |
- |
- |
+ |
- |
CSSStyleSheet.insertRule |
+ |
- |
- |
- |
+ |
+implement |
blob urls |
+ |
? |
- |
- |
- |
+implement |
All browser features |
+ |
- |
- |
? |
+ |
+implement |
Speed |
slow |
slow |
fast |
slow |
slow |
fast(real time) |
結果以下:
綜合對比,咱們的預靜態化速度最優,可在開發階段實時看到預靜態化結果,所見即所得,方便研發調試,不須要增長新的編譯階段。
遵循儘量讓頁面骨架內容顯示最終內容的原則,產出預靜態化的HTML文件,對於展現用戶個性化內容的動態組件,仍須要等待一個AJAX請求的時間,這部分組件在執行AJAX請求沒有返回以前,咱們採用了組件級別的Skeleton方案,以保證首屏使用通用內容+動態組件骨架填充:
1. 靜態組件開發時定義公共部分state,填充頁面公共部份內容,代碼示例:
this.state = { // 預靜態化環境的標記(編譯階段預靜態化插件注入) isPreRender: window.isPreRender, // 頁面公共內容 rights: [ // 權益 { dataIndex: 'cameraAction', icon: require('../../../../assets/cashVideoActivity/ricon_1.png'), button: require('../../../../assets/cashVideoActivity/do_shoot.png'), text: <span className='dt-text'>億萬獎金,<br />全網最高!</span>, action: {} // 經過 dataIndex 在接口數據裏獲取 }, { dataIndex: 'renzhengAction', icon: require('../../../../assets/cashVideoActivity/ricon_2.png'), button: require('../../../../assets/cashVideoActivity/do_auth.png'), text: <span className='dt-text'>加V認證,<br />十倍收益!</span>, action: {} } ] };
2. 動態組件根據window.isPrerender標記作預靜態化環境與瀏覽器渲染環境兩種環境下的響應,代碼示例:
// 組件骨架可以使用 http://danilowoz.com/create-content-loader/ 在線生成import ContentLoader from 'react-content-loader';const GrowthCardLoader = props => ( <ContentLoader uniquekey="growth-card-loader" animate height={68} width={320} speed={1} primaryColor="#333" secondaryColor="#333" {...props} > <rect x="0" y="0" rx="10" ry="10" width="320" height="68" /> </ContentLoader>);// 組件環境響應核心邏輯{ window.isPreRender ? ( <GrowthCardLoader className="growth-card-swiper-slide-loader"/> ): ( <正式組件 ... )}
△動態組件在兩種環境下的響應流程 通過預靜態化後的頁面首屏內容(FCP)不依賴JS執行完畢,經過一次首次渲染(FP,First Paint,首次繪製)便可向用戶展現出頁面公共內容,待AJAX返回後替換成徹底渲染的頁面。
在APP啓動時當即初始化好WebView組成的緩存池,保證加載每一個URL時省去了WebView初始化的時間,並利用上預靜態化的HTML緩存,最終使頁面無白屏加載態。
△優化效果可感知對比(左新右舊)
渲染流程改進先後對比:△渲染流程改進先後對比
直接收益:△H5性能優化直接收益
預靜態化收益:
1. 發佈前端通用高性能預靜態化插件;2. 提供了工程化引入骨架屏(Skeleton)的方式。
路由分離收益:
1. 約定式的頁面URL路徑,減少維護成本,減小先後端溝通成本;2. BrowserRouter支持,針對SPA(Single Page Application)頁面場景,支持了基於history API的BrowserRouter訪問方式;3. 刪除模版配置管理步驟,對於後端同窗能夠更加專一於數據邏輯。
前端組織從小做坊發展到了大前端時代,前端工程開發模式受到了新的挑戰如模塊化、組件化、規範化、自動化等。
前端工程本質上是軟件工程的一種。軟件工程化關注的是性能、穩定性、可用性、可維護性等方面,注重基本的開發效率、運行效率的同時,思考維護效率。採用工程化的思想管理前端工程範疇的各環節,轉化挑戰爲機遇,建立支持多團隊協做的開發流,創建堅實的工業前端基礎設施。
Web H5性能優化再也不是隻追求一兩個頁面裝載速度指標的提高,更考驗一個前端團隊的綜合能力:
1. 頂層設計能力在設計時提早佈局,尋找痛點一擊即中,並根據實際狀況不斷維護、進行調整、優化、保證規範有效性;
2. 工程管理能力用更有效的工做方法和手段使全局實踐最優,節約成本並提高研發效率;
3. 文化創新能力持續學習是最好的創新辦法,始終保持着與時俱進的基本價值觀,每一次契機均可能會促成一次成功的創新,創造獨特價值。
Web生態技術突飛猛進,性能是日趨規範化的領域,如PWA/快應用/小程序,標準化是完全解決頻發問題、改善工做質量的好辦法。
參考資料| https://github.com/stereobooster/react- snap/blob/master/doc/alternatives.md
推薦閱讀:
|百億級流量的百度搜索中臺,是怎麼作可觀測性建設的?
|十億級流量的搜索前端,是怎麼作架構升級的?
|百度信息流和搜索業務中的彈性近線計算探索與應用
---------- END ----------
百度架構師
百度官方技術公衆號上線啦!
技術乾貨 · 行業資訊 · 線上沙龍 · 行業大會
招聘信息 · 內推信息 · 技術書籍 · 百度周邊