做者:汪楠javascript
目前 Vue
、 React
在前端界混的風生水起,它們的開發思想使得咱們能真正作到先後端分離、解耦。單頁面的使用給用戶帶來了更好體驗。不過對於 Vue
和 React 這種框架來講, HTMLinJS
的思路在首屏加載慢、白屏以及 SEO
等問題就日益突出了。css
不只須要拼框架的功能、生態,固然還不能忘記「用戶至上的原理「,拼體驗。孜孜不倦的前端朋友們給出了幾個解決方案:1.Server-side rendering(SSR),2.Prerendering。下面我將一一介紹一下。html
SSR 直譯就是服務端渲染,經過設置 SSR
,你就能夠在後臺的 Node.js
環境中完成渲染邏輯,而後將 HTML
視圖直接返回給客戶端。這樣你不只可使用 Vue
和 React
技術,並且能夠直出頁面內容。而非只有一個空殼子在後端那裏。這樣也方便了搜索引擎的蜘蛛獲取頁面,解決 SEO
問題。前端
你能夠回想一下咱們在很早以前的前端開發模式,其實就是後端直出頁面。前端重構頁面,交由後端套頁面渲染首頁的數據。固然一些異步的數據,則經過 ajax
獲取數據渲染。這彷佛又回到了以前的開發模式。前端和後端仍是緊密聯繫在一塊兒了。給維護和迭代帶來的不便。vue
那它沒有好處嗎?有的!目前,社會上仍是有成熟的框架和線上的產品,好比 Nuxt.js
[1],java
, 說明它仍是具備價值的。它很明顯能夠解決首屏白屏或者SEO等問題。可是它也引入不少問題,其一,對工程師要求較高,須要同時掌握的先後端知識。其二,要考慮在服務端 Node.js
環境中的內存泄露、運行速度、併發壓力等問題。 Node
層的服務能夠用「脆弱」兩個字來形容。其三,開發成本增長,研發週期變長等。通常來講,是須要一個強大且穩定的基礎架構來支撐服務端的壓力。node
若是你能夠應付這些,無疑 SSR
對於加強應用體驗是很是棒的~,但對於像我這樣有點焦慮的人來講,是否有其餘解決辦法呢?有的, Prerendering
!webpack
有時候,咱們開發的單頁面應用也就幾個頁面,很是小型,僅僅是爲了 SEO
、首頁白屏問題,你們都以爲有點校枉過正了。能夠利用第三方插件 prerender-spa-plugin
[2],在客戶端實現渲染,這樣無需將 Vue
或者 React
代碼部署在服務端。 prerender-spa-plugin
是 Webpack
的插件,它能夠編譯應用中的全部靜態頁面,垂手可得的創建對應的索引路徑。下面結合 Vue.js
和 prerender-spa-plugin
來解決前面所提出的的問題。git
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
中的。github
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="/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="/logo.png?82b9c7a5a3f405032b1db71a25f67021"> <h1>Welcome to your prerender-spa-plugin Vuejs 2.0 demo app!</h1> <p>汪楠大大about頁</p> <p><a href="/" class="router-link-active">Home</a> <a href="/about" class="router-link-exact-active router-link-active">About</a> <a href="/contact" class="">Contact</a></p> <ul></ul> <a href="javascript:;">點擊我,看看有什麼效果</a> <p>最好不要點我</p></div></div>
<script type="text/javascript" src="/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="/joy_loading.gif?b494ac2f480615dc87d8797cb1a712da"></div></div> <!----></div></div>
<script type="text/javascript" src="/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