利用prerender-spa-plugin提高單頁面應用的體驗

做者:汪楠javascript

目前 VueReact 在前端界混的風生水起,它們的開發思想使得咱們能真正作到先後端分離、解耦。單頁面的使用給用戶帶來了更好體驗。不過對於 Vue 和 React 這種框架來講, HTMLinJS 的思路在首屏加載慢、白屏以及 SEO 等問題就日益突出了。css

不只須要拼框架的功能、生態,固然還不能忘記「用戶至上的原理「,拼體驗。孜孜不倦的前端朋友們給出了幾個解決方案:1.Server-side rendering(SSR),2.Prerendering。下面我將一一介紹一下。html

什麼是SSR?

SSR 直譯就是服務端渲染,經過設置 SSR,你就能夠在後臺的 Node.js 環境中完成渲染邏輯,而後將 HTML 視圖直接返回給客戶端。這樣你不只可使用 VueReact 技術,並且能夠直出頁面內容。而非只有一個空殼子在後端那裏。這樣也方便了搜索引擎的蜘蛛獲取頁面,解決 SEO 問題。前端

SSR有什麼問題?

你能夠回想一下咱們在很早以前的前端開發模式,其實就是後端直出頁面。前端重構頁面,交由後端套頁面渲染首頁的數據。固然一些異步的數據,則經過 ajax 獲取數據渲染。這彷佛又回到了以前的開發模式。前端和後端仍是緊密聯繫在一塊兒了。給維護和迭代帶來的不便。vue

那它沒有好處嗎?有的!目前,社會上仍是有成熟的框架和線上的產品,好比 Nuxt.js[1],java

 

, 說明它仍是具備價值的。它很明顯能夠解決首屏白屏或者SEO等問題。可是它也引入不少問題,其一,對工程師要求較高,須要同時掌握的先後端知識。其二,要考慮在服務端 Node.js 環境中的內存泄露、運行速度、併發壓力等問題。 Node 層的服務能夠用「脆弱」兩個字來形容。其三,開發成本增長,研發週期變長等。通常來講,是須要一個強大且穩定的基礎架構來支撐服務端的壓力。node

若是你能夠應付這些,無疑 SSR 對於加強應用體驗是很是棒的~,但對於像我這樣有點焦慮的人來講,是否有其餘解決辦法呢?有的, Prerenderingwebpack

Prerendering

有時候,咱們開發的單頁面應用也就幾個頁面,很是小型,僅僅是爲了 SEO、首頁白屏問題,你們都以爲有點校枉過正了。能夠利用第三方插件 prerender-spa-plugin[2],在客戶端實現渲染,這樣無需將 Vue 或者 React 代碼部署在服務端。 prerender-spa-pluginWebpack 的插件,它能夠編譯應用中的全部靜態頁面,垂手可得的創建對應的索引路徑。下面結合 Vue.jsprerender-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 版本支持 PuppeteerRendererinject 則是預渲染過程當中都能拿到的值,該值提供給你了機會,讓你以爲是否渲染這部分代碼。例以下面的代碼,是不會被預渲染進 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 優化應該不成問題了。咱們能夠更改 titlemeta 。並且頁面的內容都已經在 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 模板中,編寫對應的 titlemeta,利用 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

相關文章
相關標籤/搜索