作者簡介:王鶴,進階前端工程師,隸屬于騰訊SNG增值産品部。主要負責QQ個性化業務的功能開發及技術優化。目前專注于架構的研究,緻力于提升效能,解放生産力。
TVUE架構是WONDER和harryxiang、mitnickliu、justynchen、yucongchen、roamye等小夥伴在vuejs架構基礎上結合業務本身做的一系列優化,封裝,改進的架構實踐,同時也學習借鑒了部分QQ動漫項目組的一些優秀的思想。包含腳手架,基于QUI的VUE元件,最新的JS文法特性,PWA,内置SONIC加速方案,配套可擴充的編譯系統等。因為主要語言是用typescript編寫,是以故命名為「TVUE」架構,本文隻闡述和直出同構相關部分的内容,其它架構内的内容另行介紹。
在WONDER的[《vuejs+ts+webpack2項目實戰》][1]中,我們SNG增值産品部個性化商城業務已經用上了基于typescript、vuejs、webpack2(現在應該是webpack3)、gulp的一整套開發流程。在之前的實踐中,我們是基于純前端的VUE使用,即CDN或伺服器傳回純架構,異步JS渲染整個頁面。不過這裡缺乏頁面直出&同構的實踐場景。中型移動端項目的最佳實踐,還是基于首屏頁面直出,其它屏以元件形式異步加載的方式為佳,再結合比較成熟的SONIC加速方案提升頁面的打開速度,提升使用者體驗,而且對SEO支援友好。
大方向的技術選型WONDER在[《vuejs+ts+webpack2項目實戰》][2]中已經闡述得非常清楚了。具體細節選型,結合我們自身業務,有選擇的使用VUE提供的全家桶。
1、是否使用vue-router。根據我們自身業務的場景,比較适合用多頁面應用,路由采用後端路由,我們的後端server是TSW,後端架構是koa。使用koa在middleware中編寫router功能即可。是以我們的業務不太依賴vue-router,而且vue-router部分也可以通過緩存和異步元件自行實作。
2、是否使用vuex。對于我們的業務屬于中型項目,且我們的業務屬于多頁面應用,間接地把業務進行了二次細分。那麼VUEX反而繁瑣和不靈活,VUEX對于多頁的支援也需要改造。是以在我們的業務中,元件的傳遞都是通過props和global event bus來實作,足矣滿足我們的日常需求。
3、是否需要後端webpack打包。前端webpack打包肯定是必要的,一是檔案子產品依賴的處理,二是各種loader文法的轉換。後端是否要webpack打包這個WONDER認為可選。不打包在後端來說也是沒有問題的。打包也可以,就是釋出檔案少,扔到伺服器即可用。可能也會做一些loader處理。我們的業務暫時沒有需要後端打包。
前後端同構語言一緻這是基本。另外涉及到同構,就有兩個問題繞不開,一是采用ES6 modules 還是commonjs。二是環境不同,環境變量不同,請求通路的方式不同。
1)第一個問題,首先很遺憾Node直到最新版本也沒有支援ES6 modules 的import文法。是以後端代碼使用此文法,還需要babel等進行轉換成commonjs的模式。在我們的業務中用的是typescript的轉換能力。後端最終是commonjs,而前端要使用tree-shaking。那麼前後端最終兩者的編譯方式是不同的。
是以在我們業務中的解決方案是前端在開發環境中和後端一樣,使用commonjs的文法進行打包。然而在生産環境中,前端使用es6 modules進行打包,利用webpack的tree-shaking能力進行代碼精簡和壓縮。
有壓縮&無tree-shaking的打包大小

有壓縮&有tree-shaking的打包大小
這裡tree-shaking的效果還是蠻明顯的,有接近40%的優化。
2)第二個問題,因為前後端環境不同,比如前端有window對象,document對象,後端沒有(這裡TSW有window對象,vue的識别出現問題)。如果有這方面的相容性問題,請處理好。
Net通信并不完全一樣,前端使用的是http協定網絡通信,後端實際上從性能考慮,可以使用pb協定進行通信,不需要到http協定。當然這些在使用中倒不是瓶頸。
另外不推薦使用官方推薦的axios,我們在實踐中發現一是axios代碼非常多,源代碼多達近1600行,這在移動端确實有點浪費。另外axios還不支援常見的JSONP和getScript方式的請求方式。是以這塊建議大家根據自己需要用自己的Net庫代替。可以參考一下我們的Net庫,足夠滿足我們的業務需求。
核心代碼200行。滿足5種請求方式:
http://static.zybuluo.com/wwanghee/9ky01ng8vnwgoi9rcob48316/net.ts
先看目錄結構,基本不需要額外的介紹,主要是友善文章中代碼檔案的了解:
代碼同構一直是我們的理想編碼方式,一份代碼,前後端通用。結合VUE架構本身,VUE的SSR給我們提供了實作的可能。直出的本質無非是後端輸出一份字元串,而且結合stream,進行檔案的流式輸出。代碼類似下面這樣:
<code>view/index.first.ts</code>
雖然代碼行數不多,這裡要着重講一下,有很多細節在裡面。
1)後端部分的new Vue和前端部分的new Vue寫法略有不同:
後端部分的new Vue:
前端部分的new Vue:
<code>js/index.first.ts</code>
HTML部分
<code>html/index.html</code>
後端部分的挂載點根據,前端部分的挂載點根據元件中的<code>id="main"</code>
資料部分,後端的firstData由後端拼好資料,前端這裡有點講究。有涉及資料共享的部分。傳統的做法是通過vuex的store來實作,在我們的場景中,我們沒有使用vuex。隻是首屏渲染部分我們采取全局變量的方式來完成資料共享和一緻性。
2)context的妙用
VUE中提供的context上下文來傳遞變量給到首屏頁面是個非常友善的東西,可以做很多初始化工作。
比如我們經常需要擷取會員資訊等,定義一個全局變量可以很友善的任意地方進行使用。不需要異步加載。
再比如我們頁面做性能測試的時候,需要badjs腳本,蹦失率腳本等,且需要進行灰階處理。這使用context再友善不過了。
後端:
前端模闆:
在做了VUE同構直出之後,我們驚喜地發現我們自然而然的具備了直出和CDN頁面任意切換的能力,我們隻需要稍微改造一下就能實作。此處感謝QQ動漫團隊的靈感和啟發。
1、首屏資料部分進行一次同構,讓後端和前端都可以通過同樣的CGI取到相同的資料
<code>common/model.ts</code>
2、後端改為:
3、前端改為:
4、那麼我們建立一個名為index_cdn.html檔案
這個檔案是放在CDN的,唯一和直出檔案不同的地方就是一個
直出版本:
CDN版本:
<code>html/index_cdn.html</code>
其它完全一模一樣!
這樣我們做的事情就可以在直出Server抗不住的情況下,輕松切到CDN啦,隻不過内容全部都是異步拉取的了。對于暫時的使用者體驗來說并沒有太大影響,避免出現Server過載,業務出現無法通路的情況。
通過此方案我們可以制定一個流量控制政策,輕松在直出和CDN兩者間切換自如。
VasSonic是最近比較火的一個H5頁面加速方案,方案詳情見:https://www.qcloud.com/community/article/500037
如果在手Q環境中,那接入相當簡單。如果不是,請參考github開源代碼:https://github.com/Tencent/VasSonic
不過實際上由于VUE的一些BUG導緻接入會出現問題,好在WONDER為大家把坑填平了。
1、VUE的SSR部分無法保留注釋
看過Sonic原理和方案的同學知道Sonic是依賴注釋來拆分模闆和資料的。但是因為VUE的SSR部分代碼有個BUG,導緻無法保留注釋。
這個問題在官方文檔2.4版本已提供comments參數來解決,并且github上也有相關讨論https://github.com/vuejs/vue/pull/5951
但實際上,What a sad,并沒有徹底解決,代碼是有BUG的。
既SSR部分即使設定comments:true也是不行的。WONDER修改了兩處vue-server-render的代碼,修複了這裡的問題。準備提PR給Vue官方,看他們準備如何處理。修改如下:
6871行,源代碼為:
修改代碼為:
3012行,源代碼為:
改完代碼,隻需要輕松聲明一下注釋保留即可<code>comments: true:</code>
元件聲明部分:
2、處理好源代碼的BUG之後,我們就可以開心地使用VUE和SONIC的結合啦
1)将資料塊包裹在sonicdiff标簽中
2)引入sonic_differ子產品
3)在輸出字元串的地方用differ子產品處理一下
4)在頁面的URL參數上加一個sonicMode=3,表示在手Q環境中開啟SONIC模式(如果是非手Q環境,請按照SONIC的文檔進行接入。)
測速優化是老生常談的問題,在接入VUE的同構方案之後,我們對測速還是需要進行優化的。不過這些優化都可以在編譯流程中完成。
關于前端的測速核心還是網絡耗時+頁面耗時(首屏可互動)
網絡耗時包含伺服器的耗時+純網絡耗時。
首先直出的頁面和CDN頁面相比,服務端有渲染的耗時問題。我們之前在使用VUE直出的時候還擔心這裡會有性能問題,但實際在中型項目中使用,實驗室資料還可以,如下圖所示:
純網絡耗時比較好的思想是充分利用緩存,那麼SONIC方案就是一個很好的方案,上面已經詳細介紹過了。主要優勢展現在局部重新整理和完全Cache上面。測速資料如下:
關于頁面耗時,我們先看我們的頁面結構分解
由于我們使用VUE同構,并且對底層庫進行了大量的重構。我們的業務完全脫離zepto,隻使用qqapi的140行核心代碼。也就是我們隻依賴vue作為我們的庫。
那麼VUE庫采用手Q離線包的方案,将公共庫緩存到手Q裡,減少公共庫加載的阻塞。
index.first.js 标記為「inline」,編譯系統通過任務和插件先進行webpack的打包和tree-shaking,再識别辨別「inline」,将檔案替換為本地檔案并打在html裡面。
index.entry.js 标記為「hash」,編譯系統通過任務和插件先進行webpack的打包和tree-shaking,再識别辨別「hash」,讀取webpack的依賴聲明檔案「profile.json」,将檔案替換為hash檔案。
整個流程通過編譯系統來處理,然後交給釋出系統進行釋出。
此時目前我們實驗室的資料,頁面耗時在330ms左右。
首屏可互動的點在index.first.js中,盡管公共庫有離線包的存在,但是還是會有一些阻塞。并且index.first.js也是有執行耗時。更徹底的辦法是通過插件将首屏需要用到的監聽事件和方法抽離出來,不依賴vue公共庫,即可直接事件響應。
此處和QQ動漫團隊學習交流了一下。核心思路是把資料和小chunk方法提前到vue公共庫以前,這樣可以在沒有vue公共庫的情況下,也可以完成簡單的互動(比如跳轉,對話框,選中态等),因為在沒有VUE驅動的情況下,核心思想是需要資料和事件方法的。
在VUE
預計再提升150ms。
此篇為系列文章中的四篇中的第三篇。短期總共規劃應該有四篇,分别是:
1、《TVUE架構的腳手架&IDE環境搭建&新手必備踩坑》(作者:harryxiang, justynchen, wonderhwang)(9月初完成,目前草稿)
此篇為新手入門必備
2、《TVUE架構的初級實踐》(作者:wonderhwang)(已完成),其實就是[《vuejs+ts+webpack2項目實戰》][1]
此篇學習之後可以完成簡單的前端開發
3、《TVUE架構的中型移動端項目直出同構實踐》(作者:wonderhwang, mitnickliu, justynchen, layenlin)(已完成)。
就是本文,此篇學習後可以完成中型移動端項目的前端開發,并且提供經過線上檢驗的性能優化方案。
4、《TVUE架構的QUI》(作者:yucongchen, roamye)(十月初完成,目前草稿)
主要結合QUI元件進行快速元件開發
希望可以在基于VUE的架構之上深度挖掘,最終能提高效能和性能。早點下班回家~~