服務器端渲染的優點在於更好的seo以及更快的渲染速度,因此vue也開始支持服務器端渲染,即ssr。css
基本知識
要使用服務器端渲染,須要使用server-entry.js和client-entry.js兩個入口文件,二者都會使用到app.js進行打包,其中經過server-entry.js打包的代碼是運行在node端,二經過client-entry.js打包代碼運行在客戶端。 具體的流程圖以下所示。html
從圖上能夠看出,SSR 有兩個入口文件,client.js 和 server.js, 都包含了應用代碼,webpack 經過兩個入口文件分別打包成給服務端用的 server bundle 和給客戶端用的 client bundle. 當服務器接收到了來自客戶端的請求以後,會建立一個渲染器 bundleRenderer,這個 bundleRenderer 會讀取上面生成的 server bundle 文件,而且執行它的代碼, 而後發送一個生成好的 html 到瀏覽器,等到客戶端加載了 client bundle 以後,會和服務端生成的DOM 進行 Hydration (判斷這個 DOM 和本身即將生成的 DOM 是否相同,若是相同就將客戶端的Vue實例掛載到這個 DOM 上, 不然會提示警告)。 前端
client bundle就是vue-ssr-client-manifest.json,而server bundle就是vue-ssr-server-bundle.json,這兩個文件都是很是容易獲取的
vue
在純前端渲染時,通常使用的是web-dev-server這個插件,它能夠自動幫助咱們開啓一個node端,主要做用是監控並打包代碼,可是實際上仍是純前端渲染,另外配合web-hot-middleware來進行HMR熱更新,這樣能夠在咱們改變了代碼以後自動打包並更新view,以此來提升開發效率。node
而在vue服務器端渲染時,就不能只是使用web-dev-server和web-hot-middle了,由於咱們須要添加服務器渲染的node代碼邏輯,這樣,咱們能夠本身開一個node服務器,使用webpack-dev-middle中間件進行打包、使用webpack-hot-middle中間件進行熱更新,並添加服務器端渲染邏輯,即node端經過引入vue-serverer-renderer插件來渲染服務器端打包的bundle文件到客戶端。 react
而在服務器端的配置文件webpack.server.js以下所示:webpack
const webpack = require('webpack') const merge = require('webpack-merge') const base = require('./webpack.base.config') const nodeExternals = require('webpack-node-externals') const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') module.exports = merge(base, { target: 'node', devtool: '#source-map', entry: './client/entry-server.js', output: { filename: 'server-bundle.js', libraryTarget: 'commonjs2' }, externals: nodeExternals({ // do not externalize CSS files in case we need to import it from a dep whitelist: /\.css$/ }), plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 'process.env.VUE_ENV': '"server"', 'process.env.VUE_HOST': JSON.stringify(process.env.VUE_HOST || 'http://localhost:8888') }), new VueSSRServerPlugin() ] })
即經過webpack-merge來merge到webpack.base.config的文件,接着具體配置中,target: 'node'表示在node端運行,devtools: '#source-map'能夠保證報錯時定位到出錯文件的行數,output.filename表示輸出文件的名稱,而output.libraryTarget表示模塊入口形式,external表示外部不須要打包,最後就是使用到的plugins。git
而以前所提到的webpack-dev-middleware和webpack-hot-middleware這兩個插件能夠配置在setup-dev-server.js中的,在index.js中配置服務器時,若是是開發環境,再引入setup-dev-server.js,不然不須要引入。github
因此在bi項目中的兩個服務器實際上都是node服務器,其中index.js建立的服務器是node服務器,提供ssr和其餘的一些服務,而server下中src的index.js是一個node中間件服務器,起到的是代理服務的做用。web
注意事項
1. webpack打包是須要在服務器端進行的。即webpack在以前純前端渲染時都是跑在瀏覽器端的,而若是要使用服務器端渲染,就須要讓webpack打包這個過程代碼跑在服務器端了。由於vue文件是用.vue形式組織的,因此必須在服務器端打包才能進行服務器端渲染,而且由於在客戶端請求是單頁的,因此服務器端打包也應該打包爲單個文件。
2. 服務器端index.js流程是如何的? 即npm run dev以後,就會進入index.js,而後引入express做爲node服務器,並引入vue-server-renderer來集成,進一步將vue的app來服務器渲染,可是如何在服務器端獲取這個打包以後的app呢? 即經過entry-server.js便可,這個入口文件就會打包node端運行的vue,打包以後,node端會生成了html標記,而後須要一層html外殼,即套用index.html模板template,這個模板中有一個<!--vue-ssr-outlet>註釋,表示
3. 既然有了服務器端渲染,爲何項目中還須要client-server進行客戶端的webpack打包呢?
(1)服務器須要在服務器端打包而後進行渲染,而客戶端打包bundle是須要將客戶端bundle給瀏覽器進行混合靜態標記。那麼爲何須要混合靜態標記呢? 默認狀況下,能夠在瀏覽器中輸出 Vue 組件,進行生成 DOM 和操做 DOM。然而,也能夠將同一個組件渲染爲服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最後將靜態標記"混合"爲客戶端上徹底交互的應用程序.如上,由於服務器端發送來的是html字符串,還不是應用程序,好比沒有css等,這樣,就須要在客戶端進行打包,而後混合爲應用程序。
(2)另外一個須要關注的問題是在客戶端,在掛載(mount)到客戶端應用程序以前,須要獲取到與服務器端應用程序徹底相同的數據 - 不然,客戶端應用程序會由於使用與服務器端應用程序不一樣的狀態,而後致使混合失敗。
(3) 客戶端只須要拿到簡單的vue打包文件,這個文件是一個模板,而不須要獲取到具體的數據。而服務器端是在獲取到數據以後拼接html發送,因此須要在客戶端進行整合。即服務器端代碼打包是爲了提供最開始的html頁面,而客戶端代碼打包是爲了後期的數據和交互,因此,並非只須要服務器端打包便可。
4. 配置webpack.server.config時爲何externals中包含css文件?由於此功能webpack沒法在node端執行。
5. 服務器端獲取打包以後的代碼是和客戶端同樣都是js文件嗎? 不是的,通常來講,服務器端在獲取webpack打包的代碼應該是 built-server-bundle.js,可是這樣每次在編輯過應用程序代碼以後都須要再從新重啓,會影響開發效率,另外nodejs不支持source map。因此,咱們可使用 bundlerender,這種方式和render是相似的,但它支持sourcemap,熱重載等。在webpack.server.config文件中配置了插件new VueSSRServerPlugin(),這個插件的做用是做爲整個服務器的輸出爲json文件,而再也不是js文件,默認文件名爲 vue-ssr-server-bundle.json。
6. 在app.js中,createApp函數的做用是什麼?這是爲了每一個用戶獲得一份新的實例,防止狀態污染。
7. 總體過程究竟是怎樣的?即首先寫好各類組件、路由、store等,接着app.js中開始進行匯聚,而後entry-client.js和entry-server.js分別進行對二者的整合。接下來就能夠build了,在build客戶端代碼的時候即經過webpack.client.js進入,入口文件爲entry-client.js,最後會打包完整的代碼;在build服務器端代碼的時候經過webpack.server.js進入,入口文件爲entry-server.js,會打包出vue-ssr-server-bundle.json文件;固然這些打包後的文件都會打包到dist文件夾下。build以後,就能夠把代碼放在服務器上運行了,即經過node建立一個服務器進行服務器端渲染。
8. 查看服務器端渲染代碼? 使用查看源代碼便可。 好比使用vue和react作出的網站是SPA,那麼經過view source得到的代碼必定是一個html框架,即<html><head></head><body> ' 這裏是空的' </body></html>,即在body標籤中是不存在html代碼的,這樣的結果就是很是不利於seo,且這表示它是沒有作服務器端渲染的。 而若是一個作了服務器端渲染的vue網站,view source獲得的代碼中html是填滿的,且包括了全部數據,這就表示這個數據是經過服務器端渲染獲得的。或者直接在network中查看接受到的html頁面便可 。若是作了ssr,那麼獲得的html是包含插入的css的,由於須要到客戶端就顯示,因此html和css都是須要的,而JavaScript對於顯示而言並非必要的,因此服務器端渲染到的html中不須要引入額外的JavaScript(除了自身寫入的)。
9. vue ssr中,爲何組件已經用preFetch在服務器端獲取數據,客戶端還須要再去fetch? 服務端已經 preFetch 的客戶端固然不用 fetch。preFetch 的數據是存進了服務端 vuex store 裏面,而後這些數據會直接內聯在直出的 HTML 裏面。客戶端的 vuex store 啓動的時候就直接以這些數據爲初始數據了。客戶端組件調用 actions 的時候,vuex store 起到一層緩存的做用,已經有的數據不會再 fetch。
!! 因此能夠認爲是服務器端在接收到url以後,就開始router.push(url),而後由於服務器端也有對應的打包,因此會請求數據並拼接獲得完整的html頁面,並把服務器端vuex的state賦值給context.state並傳遞給前端,前端拿到這個state以後,就會用這個數據,而不會繼續請求新的數據。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="./test1.css"> <link rel="preload" href="./test2.css" as="style"> <link rel="preload" href="./test1.js" as="script"> </head> <body> <h1>title</h1> <p>some contents</p> <script src="./test2.js"></script> </body> </html>
如上,preload使用須要rel中聲明,而且須要使用as標記格式。加載順序爲 test2.css -> test1.js -> test1.css -> test2.js,即對於preload的文件會最早加載。可是在瀏覽器執行時能夠發現,preload的文件只是加載可是沒有使用,可是若是在下面又但願引入,這時引入會很快,不會從新下載,即後面但願用到的時候立馬有效,能夠解決不少問題。 注意: preload是在當前頁面中使用的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="./test1.css"> <link rel="prefetch" href="./test2.css"> <link rel="prefetch" href="./test1.js"> </head> <body> <h1>title</h1> <p>some contents</p> <script src="./test2.js"></script> </body> </html>
如上而prefetch的使用,它不須要添加as來標記類型,加載順序爲 test1.css -> test2.js -> test2.css -> test1.js,通常prefetch會最後加載,因此,使用prefetch每每是用在下一頁可能會用到,這種狀況也比較常見,這樣用戶在點入下一個可能的頁面時,因爲數據已經加載到,那麼速度就很是快了。
18. 在服務器端渲染獲得頁面view source時,能夠看到 data-server-rendered = "true",是什麼意思?data-server-rendered
特殊屬性,讓客戶端 Vue 知道這部分 HTML 是由 Vue 在服務端渲染的,而且應該以激活模式進行掛載。注意,這裏並無添加 id="app"
,而是添加 data-server-rendered
屬性:你須要自行添加 ID 或其餘可以選取到應用程序根元素的選擇器,不然應用程序將沒法正常激活。在開發模式下,Vue 將推斷客戶端生成的虛擬 DOM 樹(virtual DOM tree),是否與從服務器渲染的 DOM 結構(DOM structure)匹配。若是沒法匹配,它將退出混合模式,丟棄現有的 DOM 並從頭開始渲染。在生產模式下,此檢測會被跳過,以免性能損耗。
即咱們能夠區分是不是服務器端渲染,只要存在data-server-renderer="true"便可判斷。
可參考這篇文章: https://zhuanlan.zhihu.com/p/25936718
而代碼大部分是參考: https://github.com/vuejs/vue-hackernews-2.0