標籤:

從單頁應用(SPA)到伺服器渲染(SSR)

系列文章:

  1. Vue 2.0 升(cai)級(keng)之旅

  2. Vuex — The core of Vue application

  3. 從單頁應用(SPA)到伺服器渲染(SSR)(本文)

個人博客之前已經將 vue-router 的模式改為了 history,即 url 中不包含 hash,再通過將所有的靜態請求轉發到 index.html,使它看上去似乎像一個靜態多頁的網站。

然而,它其實和其他的 SPA (Single Page Application 單頁應用)來說沒有任何的區別,最終是通過前端的路由去控制頁面的顯示。單頁應用雖然在交互體驗上比傳統多頁更友好,但它也有一個天生的缺陷,就是對搜索引擎不友好,不利於爬蟲爬取數據。

正所謂成也蕭何,敗也蕭何。

講人話就是,搜索引擎搜不到我的博客啊~哭...

那什麼對搜索引擎和爬蟲友好的哪?答案就是靜態頁,而非瀏覽器渲染,這就需要伺服器直接渲染,也就 SSR(Server Side Render)。

SSR,伺服器渲染。簡單來說就是,伺服器將每個要展示的頁面都運行完成後,將整個相應流傳送給瀏覽器,所有的運算在伺服器端都已經完成,瀏覽器只需要解析 HTML 就行。

說起來簡單,那到底該如何著手將項目改造成 SSR,和曾經的多頁又有什麼區別哪?既然自己在 SSR 方面是個小白,自然要先從查資料看文檔入手,Vue 2.0 的文檔中有一章就是關於 SSR。

看了文檔之後,它給了我一個新思路,可以在無須大幅修改原先代碼的情況下做到 SSR,又不失單頁良好的體驗。

聽上去很酷是不是,具體怎麼做繼續看下去。

SSR Architecture

一個普通的單頁應用通常是通過 webpack 將源代碼打包後插入到 html 中,當頁面請求時,返回 html 再載入打包後的 js 文件,也就是下圖中的 Application Code,Webpack build 和 browser 這三大塊。

剩下的那幾部分就是 SSR 需要額外新加的部分,一個個來看。

Server entry & Client entry

Server entry & client entry 兩者的有共同的詞尾 entry,對應的是 webpack.config 中的 entry,即打包入口文件,也就是分別代表伺服器端所運行代碼的入口和瀏覽器端所運行代碼的入口文件。

入口文件自然不用多複雜。

  • server entry: 根據路由狀態,返回渲染完成後相應的組件
  • clinet entry: 將應用直接掛載到 DOM 上

OK。它倆的事就做完啦,是不是很簡單。

Webpack build

有了不同的 entry,打包的內容也有不同,自然就要兩套配置。

配置 webpack 的配置文件的確很麻煩,但有個好消息就是原先的打包文件不需要修改,只需加一個 server 端的配置文件就可以了。server 端的配置文件也相當簡單,基本可以沿用客戶端的配置,改改 entry 和 output 基本就差不多了。

不過,有一點要注意,一定要將 target 屬性設置成 node,不然打包完了也沒法在 node 環境下跑。還可以將所有依賴都設置成 externals(跑在伺服器本地嘛,依賴自然都拿得到),這只是個優化點,不加也沒有任何問題。

有了配置文件,也就能生成 Server Bundle 了,只剩下最後一塊 Bundle Renderer 了。

Bundle Renderer

到這裡才要用上 vue 為支持 ssr 所依賴的庫 vue-server-renderer。

通過 vue-server-renderer 提供的 API 就能容易地根據 url 生成對應的組件樹,然後將它返回給客戶端。

這裡要注意,因為用的是 webpack 打包後的文件,所以只能用 createBundleRenderer 而不能用 createRenderer 來創建 renderer。

創建 renderer 的時候還可以為它配置 cache,方法在 README 中也寫得很清楚了,由於我個人博客的場景不適合添加 cache 就沒有添加。

這樣從 SPA 到 SSR 的變更就完成了,通過瀏覽器訪問看看是不是已經將頁面整個返回了。

Tips

  • 遇到控制台 ??

The client-side rendered virtual DOM tree is not matching server-rendered content.

當然,可能是你的標籤不對應,也有可能是 text node 中的空格字元長度不對應,我個人遇到的都是空格不對應造成的問題,很是尷尬(可能是使用 template 語法造成的)...

  • Memory-fs

在開發環境下,由於使用伺服器渲染,自然不能使用 webpack-dev-server,而是要用 webpack-dev-middleware。然而,webpack-dev-middleware 所創建的文件都是在內存里的,server 就無法讀到 server bundle 文件,這裡就要用到 memory-fs 來從內存中讀文件。

  • KOA 2

用 koa 2 作為伺服器時,在 renderToString 或 renderToStream 時,記得外面要加 await,否則,程序就不等組件渲染好,就直接跑下個 middleware 去了。

(奉勸大家不要用 koa 作 SSR 伺服器,koa 和 webpack-dev-middleware 天生水土不服,不要問我為什麼~??)

  • document

在 Server 端渲染時,node 環境下是沒有 document 對象的。當一個界面的顯示依賴於 document 對象(比如,頁面滾動監聽事件),那麼,在 node 端運行時就會報錯。這時,有兩個解決的辦法。

  1. 根據運行時的環境變數,通過添加邏輯來判斷是否依賴 document
  2. 使用 jsdom mock document 對象(個人偷懶的做法)

當然,從設計的角度移除對 document 的依賴就最好啦。

  • $root._isMounted:組件中可以用這個參數來判斷應用是否為第一次掛載

完成

這樣當瀏覽器請求時,返回的頁面是伺服器渲染之後的,瀏覽器解析後,頁面仍就是一個單頁應用。

最後,看效果的戳這裡,看代碼的戳這裡,原先 SPA 的代碼依舊保留在了 SPA 分支。

對 Vue SSR 有興趣的童鞋,一定要看看 vue hackernews 2.0,大神的水準比我可是高多了。

最後的最後,吐槽下 Daocloud,最近老掛我伺服器,枉我一直為它說好話。

自己寫完,看看感覺好簡單,為什麼還搞了那麼久...

推薦閱讀:

現代 CSS 進化史
論前端開發如何把AI帶進項目
前端日刊-2018.02.10
web前端:如何(安全地)使用Vue.js的jQuery插件
探秘 React Hot Loader

TAG:前端開發 |