作者:汪楠
簡介: Vue、React單頁面的應用給開發者和用戶都帶來了更好的體驗,但是首屏加載慢、白屏和SEO等問題也日益突出,本文主要針對此用戶體驗問題給出漸進的處理方式-預渲染
目前
Vue
、
React
在前端界混的風生水起,它們的開發思想使得我們能真正做到前後端分離、解耦。單頁面的使用給用戶帶來了更好體驗。不過對於
Vue
和 React 這種框架來說,
HTMLinJS
的思路在首屏加載慢、白屏以及
SEO
等問題就日益突出了。
不僅需要拼框架的功能、生態,當然還不能忘記“用戶至上的原理“,拼體驗。孜孜不倦的前端朋友們給出了幾個解決方案:1.Server-side rendering(SSR),2.Prerendering。下面我將一一介紹一下。
SSR 直譯就是服務端渲染,通過設置
SSR
,你就可以在後臺的
Node.js
環境中完成渲染邏輯,然後將
HTML
視圖直接返回給客戶端。這樣你不僅可以使用
Vue
和
React
技術,而且可以直出頁面內容。而非只有一個空殼子在後端那裏。這樣也方便了搜索引擎的蜘蛛獲取頁面,解決
SEO
問題。
你可以回想一下我們在很早之前的前端開發模式,其實就是後端直出頁面。前端重構頁面,交由後端套頁面渲染首頁的數據。當然一些異步的數據,則通過
ajax
獲取數據渲染。這似乎又回到了之前的開發模式。前端和後端還是緊密聯繫在一起了。給維護和迭代帶來的不便。
那它沒有好處嗎?有的!目前,社會上還是有成熟的框架和線上的產品,比如
Nuxt.js
[1],
, 說明它還是具有價值的。它很明顯可以解決首屏白屏或者SEO等問題。但是它也引入很多問題,其一,對工程師要求較高,需要同時掌握的前後端知識。其二,要考慮在服務端
Node.js
環境中的內存泄露、運行速度、併發壓力等問題。
Node
層的服務可以用“脆弱”兩個字來形容。其三,開發成本增加,研發週期變長等。一般來說,是需要一個強大且穩定的基礎架構來支撐服務端的壓力。
如果你可以應付這些,無疑
SSR
對於增強應用體驗是非常棒的~,但對於像我這樣有點焦慮的人來說,是否有其他解決辦法呢?有的,
Prerendering
!
有時候,我們開發的單頁面應用也就幾個頁面,非常小型,僅僅是爲了
SEO
、首頁白屏問題,大家都覺得有點校枉過正了。可以利用第三方插件
prerender-spa-plugin
[2],在客戶端實現渲染,這樣無需將
Vue
或者
React
代碼部署在服務端。
prerender-spa-plugin
是
Webpack
的插件,它可以編譯應用中的所有靜態頁面,輕而易舉的建立對應的索引路徑。下面結合
Vue.js
和
prerender-spa-plugin
來解決前面所提出的的問題。
安裝
npm install prerender-spa-plugin --save-dev
使用
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
module.exports = {
plugins: [
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: [ '/', '/about', '/contact' ],
renderer: new Renderer({
inject: {
foo: 'bar'
},
renderAfterDocumentEvent: 'render-event'
})
})
])
]
}
staticDir
指的是預渲染輸出的頁面地址,
routes
指的是需要預渲染的路由地址,
renderer
則是所採用的渲染引擎是什麼,目前用的是
V3.4.0
版本支持
PuppeteerRenderer
。
inject
則是預渲染過程中都能拿到的值,該值提供給你了機會,讓你覺得是否渲染這部分代碼。例如下面的代碼,是不會被預渲染進
HTML
中的。
showMessage(){
if(window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.foo =='bar') return;
this.message = '我是測試預加載攔截';
}
renderAfterDocumentEvent
這個則很關鍵,這個是監聽
document.dispatchEvent
事件,決定什麼時候開始預渲染。
new Vue({
el: '#app',
router,
render: h => h(App),
mounted () {
// You'll need this for renderAfterDocumentEvent.
document.dispatchEvent(new Event('render-event'))
}
});
實例
具體可以看一下官方的
Vue.js2.0+vue-routerPrerenderSPAExample
[3] 實例。
原理及缺點
prerender-spa-plugin
利用了
Puppeteer
[4] 的爬取頁面的功能。
Puppeteer
是一個
Chrome
官方出品的
headlessChromenode
庫。它提供了一系列的 API, 可以在無 UI 的情況下調用
Chrome
的功能, 適用於爬蟲、自動化處理等各種場景。它很強大,所以很簡單就能將運行時的
HTML
打包到文件中。原理是在
Webpack
構建階段的最後,在本地啓動一個
Puppeteer
的服務,訪問配置了預渲染的路由,然後將
Puppeteer
中渲染的頁面輸出到
HTML
文件中,並建立路由對應的目錄。
利用官方的實例進行編譯結果如下:
每個對應的路都有一個對應的靜態
HTML
。每一個
HTML
內除了
<div id="app"></div>
這個
Vue
的掛載元素外,還有靜態的標籤內容。
<!DOCTYPE html><html lang="en"><head>
<meta charset="utf-8">
<title>PRODUCTION prerender-spa-plugin</title>
<link rel="shortcut icon" href="https://jdc.jd.com/favicon.ico"><style type="text/css"></style><style type="text/css">#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px}h1,h2{font-weight:400}ul{list-style-type:none;padding:0}li{display:inline-block;margin:0 10px}a{color:#42b983}</style></head>
<body>
<div id="app"><div><img src="https://jdc.jd.com/logo.png?82b9c7a5a3f405032b1db71a25f67021"> <h1>Welcome to your prerender-spa-plugin Vuejs 2.0 demo app!</h1> <p>汪楠大大about頁</p> <p><a href="https://jdc.jd.com/" class="router-link-active">Home</a> <a href="https://jdc.jd.com/about" class="router-link-exact-active router-link-active">About</a> <a href="https://jdc.jd.com/contact" class="">Contact</a></p> <ul></ul> <a href="javascript:;">點擊我,看看有什麼效果</a> <p>最好不要點我</p></div></div>
<script type="text/javascript" src="https://jdc.jd.com/build.js"></script>
</body></html>
既然有了每個路由對應的
HTML
,那麼對應
SEO
優化應該不成問題了。我們可以更改
title
、
meta
。而且頁面的內容都已經在
HTML
中直接呈現,就不會有因爲
js
等資源加載慢導致白屏的問題。
prerender-spa-plugin
的確在一定程度上解決了我們對於
SEO
的訴求和頁面加載慢的問題。但是它的缺點還是很明顯的。
- 不同的用戶看到不同的頁面,動態數據頁面
- 經常發生變化的頁面,數據實時性展示(比如體育比賽等)
- 路由過多,構建時間過長
聊了這麼多,大家都是爲了給用戶更好的體驗。基本上我們做的頁面都是強依賴動態數據展示,如果渲染的靜態頁面和最後呈現的頁面之前切換並不自然,那麼體驗是很差的。我們不能爲了解決
SEO
或者加載慢的問題,引入新的問題。那到底該怎麼做呢?其實很簡單:
Loading
或者 骨架屏。這兩個都是將過渡的
HTML
片段插入到
<divid="app"></div>
中。一旦
JavaScript
加載完,
Vue
開始渲染正式頁面時候,就將把過渡的
HTML
片段幹掉了。看一下下面的代碼
body>
<div id="app"><div><div class="j-loading-wrap"><div class="j-mask"></div> <div class="j-loading"><img src="https://jdc.jd.com/joy_loading.gif?b494ac2f480615dc87d8797cb1a712da"></div></div> <!----></div></div>
<script type="text/javascript" src="https://jdc.jd.com/build.js"></script>
</body>
<skeleton-loading >
<row
:gutter="{
bottom: '0.1rem'
}">
<column :span="'24'">
<square-skeleton
:boxProperties="{
height: '0.3rem'
}"
/>
</column>
</row>
<row>
<square-skeleton
:boxProperties="{
height: '3.1rem'
}"
/>
</row>
<skeleton-loading >
Loading
或者骨架屏只是爲了增強用戶體驗,那麼跟本文的主體有什麼關係呢?有關係,一般在做過渡效果的時候,很多都是手寫代碼,這樣既不利於維護,也不利於統一標準。我們可以使用
prerender-spa-plugin
進行操作,它能把整個頁面都生成靜態文件輸出到
HTML
,那麼對於我們的
Loading
組件或者頁面首屏的
DOM
和樣式自動化的輸出到
HTML
中,不是輕而易舉的事情嗎?並且對於骨架屏來說,它只需要展示首屏的內容,所以我們可以利用插件的全局變量,進行判斷,是否需要後續的抓取頁面動作。這樣也解決了骨架屏的體積大小問題。
再則,
SEO
的問題對於目前開發的應用,很少有要求,基本都是入口頁。但是用
prerender-spa-plugin
也可以完美的解決,生成
Loading
的多個路由頁面或者骨架屏的多個路由頁面,都可以由後端部署到
vm
模板中,編寫對應的
title
和
meta
,利用
SEO
的優化。
以上是自動化抓取頁面生成骨架屏,但目前還是處於研究階段,目前我們在生產項目中用的仍然是手寫的骨架屏組件,可以參考這個
vue-skeleton-loading
[5] 組件。利用預渲染的插件將骨架屏
Loading
組件或者標準的
Loading
組件以
DOM
形式輸出到部署生產的
HTML
頁面中。整體的Webpack構建環境是採用JDC前端開發部團隊對搭建的vue-cli腳手架Gaea,編寫完loading或者骨架屏部分的代碼後,可以配置輸出的路由地址,利用npm run html 的命令輸出對應的html文件。組件則是來自於JDC前端開發部團隊研發的輕量級的、廣泛使用在京東APP、京東Me等移動端場景的Vue組件庫:NutUI[6]。預渲染插件使得我們能輕鬆的將公用組件插入到html中,從而解決上面說到的問題,不失爲一個漸進的解決方案。
本文羅列了單頁面體驗的痛點:首屏加載慢、白屏的問題以
SEO
。也給出了漸進的解決方案利用預渲染
prerender-spa-plugin
的輸出
Vue
或者
React
公用組件(骨架屏組件和
Loading
組件)到各個路由頁面
HTML
中。後續將依賴預渲染插件進行自動化骨架屏的輸出方案,歡迎討論和交流,敬請關注~
[1] Nuxt.js:https://nuxtjs.org/guide
[2] prerender-spa-plugin:https://github.com/chrisvfritz/prerender-spa-plugin
[3] vue2-webpack-router:https://github.com/chrisvfritz/prerender-spa-plugin/tree/dba55854a95a7a4e9b4aaf4203fb0563739bc58a/examples/vue2-webpack-router
[4] puppeteer: https://github.com/GoogleChrome/puppeteer
[5] vue-skeleton-loading:https://github.com/jiingwang/vue-skeleton-loading
[6] NutUI: http://nutui.jd.com/
文章來源於 全棧探索 微信公衆號,掃描下面二維碼關注: