最近簡單的研究了一下SSR,對SSR已經(jīng)有了一個(gè)簡單的認(rèn)知,主要應(yīng)用于單頁面應(yīng)用,Nuxt是SSR很不錯(cuò)的框架。也有過調(diào)研,簡單的用了一下,感覺還是很不錯(cuò)。但是還是想知道若不依賴于框架又應(yīng)該如果處理SSR,研究一下做個(gè)筆記。
什么是SSR
把Vue組件渲染為服務(wù)器端的HTML字符串,將他們直接發(fā)送到瀏覽器,最后將靜態(tài)標(biāo)記混合為客戶端上完全交互的應(yīng)用程序。
為什么要使用SSR
SSR弊端
準(zhǔn)備工作
在正式開始之前,在vue官網(wǎng)找到了一張這個(gè)圖片,圖中詳細(xì)的講述了vue中對ssr的實(shí)現(xiàn)思路。如下圖簡單的說一下。
下圖中很重要的一點(diǎn)就是webpack,在項(xiàng)目過程中會(huì)用到webpack的配置,從最左邊開始就是我們所寫入的源碼文件,所有的文件都有一個(gè)公共的入口文件app.js,然后就進(jìn)入了server-entry(服務(wù)端入口)和client-entry(客戶端入口),兩個(gè)入口文件都要經(jīng)過webpack,當(dāng)訪問node端的時(shí)候,使用的是服務(wù)端渲染,在服務(wù)端渲染的時(shí)候,會(huì)生成一個(gè)server-Bender,最后通過server-Bundle可以渲染出HTML頁面,若在客戶端訪問的時(shí)候則是使用客戶端渲染,通過client-Bundle在以后渲染出HTML頁面。so~通過這個(gè)圖可以很清晰的看出來,接下來會(huì)用到兩個(gè)文件,一個(gè)server入口,一個(gè)client入口,最后由webpack生成server-Bundle和client-Bundle,最終當(dāng)去請求頁面的時(shí)候,node中的server-Bundle會(huì)生成HTML界面通過client-Bundle混合到html頁面中即可。

對于vue中使用ssr做了一些簡單的了解之后,那么就開始我們要做的第一步吧,首先要?jiǎng)?chuàng)建一個(gè)項(xiàng)目,創(chuàng)建一個(gè)文件夾,名字不重要,但是最好不要使用中文。
mkdir domecd domenpm init
npm init命令用來初始化package.json文件:
{ "name": "dome", // 項(xiàng)目名稱 "version": "1.0.0", // 版本號 "description": "", // 描述 "main": "index.js", // 入口文件 "scripts": { // 命令行執(zhí)行命令 如:npm run test "test": "echo /"Error: no test specified/" && exit 1" }, "author": "Aaron", // 作者 "license": "ISC" // 許可證}初始化完成之后接下來需要安裝,項(xiàng)目所需要依賴的包,所有依賴項(xiàng)如下:
npm install express --save-devnpm install vue --save-devnpm install vue-server-render --save-devnpm install vue-router --save-dev
如上所有依賴項(xiàng)一一安裝即可,安裝完成之后就可以進(jìn)行下一步了。前面說過SSR是服務(wù)端預(yù)渲染,所以當(dāng)然要?jiǎng)?chuàng)建一個(gè)Node服務(wù)來支撐。在dome文件夾下面創(chuàng)建一個(gè)index.js文件,并使用express創(chuàng)建一個(gè)服務(wù)。
代碼如下:
const express = require("express");const app = express();app.get('*',(request,respones) => { respones.end("ok");})app.listen(3000,() => { console.log("服務(wù)已啟動(dòng)")});完成上述代碼之后,為了方便我們需要在package.json添加一個(gè)命令,方便后續(xù)開發(fā)啟動(dòng)項(xiàng)目。
{ "scripts": { "test": "echo /"Error: no test specified/" && exit 1", "start": "node index.js" }}創(chuàng)建好之后,在命令行直接輸入npm start即可,當(dāng)控制臺(tái)顯示服務(wù)已啟動(dòng)則表示該服務(wù)已經(jīng)啟動(dòng)成功了。接下來需要打開瀏覽器看一下渲染的結(jié)果。在瀏覽器地址欄輸入locahost:3000則可以看到ok兩個(gè)字。
SSR渲染手動(dòng)搭建
前面的準(zhǔn)備工作已經(jīng)做好了,千萬不要完了我們的主要目的不是為了渲染文字,主要的目標(biāo)是為了渲染*.vue文件或html所以。接下來就是做我們想要做的事情了。接下來就是要修改index.js文件,將之前安裝的`vue
和vue-server-render`引入進(jìn)來。
由于返回的不再是文字,而是html模板,所以我們要對響應(yīng)頭進(jìn)行更改,告訴瀏覽器我們渲染的是什么,否則瀏覽器是不知道該如何渲染服務(wù)器返回的數(shù)據(jù)。
在index.js中引入了vue-server-render之后,在使用的時(shí)候,我們需要執(zhí)行一下vue-server-render其中的creteRender方法,這個(gè)方法的作用就是會(huì)將vue的實(shí)例轉(zhuǎn)換成html的形式。
既然有了vue-server-render的方法,接下來就需要引入主角了vue,引入之后然后接著在下面創(chuàng)建一個(gè)vue實(shí)例,在web端使用vue的時(shí)候需要傳一些參數(shù)給Vue然而在服務(wù)端也是如此也可以傳遞一些參數(shù)給Vue實(shí)例,這個(gè)實(shí)例也就是后續(xù)添加的那些*.vue文件。為了防止用戶訪問的時(shí)候頁面數(shù)據(jù)不會(huì)互相干擾,暫時(shí)需要把實(shí)例放到get請求中,每次有訪問的時(shí)候就會(huì)創(chuàng)建一個(gè)新的實(shí)例,渲染新的模板。
creteRender方法能夠把vue的實(shí)例轉(zhuǎn)成html字符串傳遞到瀏覽器。那么接下來由應(yīng)該怎么做?在vueServerRender方法下面有一個(gè)renderToString方法,這個(gè)方法就可以幫助我們完成這步操作。這個(gè)方法接受的第一個(gè)參數(shù)是vue的實(shí)例,第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù),如果不想使用回調(diào)函數(shù)的話,這個(gè)方法也返回了一個(gè)Promise對象,當(dāng)方法執(zhí)行成功之后,會(huì)在then函數(shù)里面返回html結(jié)構(gòu)。
改動(dòng)如下:
const express = require("express");const Vue = require("vue");const vueServerRender = require("vue-server-render").creteRender();const app = express();app.get('*',(request,respones) => { const vueApp = new Vue({ data:{ message:"Hello,Vue SSR!" }, template:`<h1>{{message}}</h1>` }); respones.status(200); respones.setHeader("Content-Type","text/html;charset-utf-8;"); vueServerRender.renderToString(vueApp).then((html) => { respones.end(html); }).catch(error => console.log(error));})app.listen(3000,() => { console.log("服務(wù)已啟動(dòng)")});上述操作完成之后,一定要記得保存,然后重啟服務(wù)器,繼續(xù)訪問一下locahost:3000,就會(huì)看到在服務(wù)端寫入的HTML結(jié)構(gòu)了。這樣做好像給我們添加了大量的工作,到底與在web端直接使用有什么區(qū)別么?
接下來見證奇跡的時(shí)刻到了。在網(wǎng)頁中右鍵查看源代碼就會(huì)發(fā)現(xiàn)與之前的在web端使用的時(shí)候完全不同,可以看到渲染的模板了。如果細(xì)心的就會(huì)發(fā)現(xiàn)一件很有意思的事情,在h1標(biāo)簽上會(huì)有一個(gè)data-server-rendered=true這樣的屬性,這個(gè)可以告訴我們這個(gè)頁面是通過服務(wù)端渲染來做的。大家可以去其他各大網(wǎng)站看看哦。沒準(zhǔn)會(huì)有其他的收獲。
上面的案例中,雖然已經(jīng)實(shí)現(xiàn)了服務(wù)端預(yù)渲染,但是會(huì)有一個(gè)很大的缺陷,就是我們所渲染的這個(gè)網(wǎng)頁并不完整,沒有文檔聲明,head等等等,當(dāng)然可能會(huì)有一個(gè)其他的想法,就是使用es6的模板字符串做拼接就好了啊。確實(shí),這樣也是行的通的,但是這個(gè)仍是飲鴆止渴不能徹底的解決問題,如果做過傳統(tǒng)MVC開發(fā)的話,就應(yīng)該知道,MVC開發(fā)模式全是基于模板的,現(xiàn)在這種與MVC有些相似的地方,同理也是可以使用模板的。在dome文件夾下創(chuàng)建index.html,并創(chuàng)建好HTML模板。
模板現(xiàn)在有了該如何使用?在creteRender函數(shù)可以接收一個(gè)對象作為配置參數(shù)。配置參數(shù)中有一項(xiàng)為template,這項(xiàng)配置的就是我們即將使用的Html模板。這個(gè)接收的不是一個(gè)單純的路徑,我們需要使用fs模塊將html模板讀取出來。
其配置如下:
let path = require("path");const vueServerRender = require("vue-server-render").creteRender({ template:require("fs").readFileSync(path.join(__dirname,"./index.html"),"utf-8")});現(xiàn)在模板已經(jīng)有了,在web端進(jìn)行開發(fā)的時(shí)候,需要掛在一個(gè)el的掛載點(diǎn),這樣Vue才知道把這些template渲染在哪,服務(wù)端渲染也是如此,同樣也需要告訴Vue將template渲染到什么地方。接下來要做的事情就是在index.html中做手腳。來通知creteRender把template添加到什么地方。
更改index.html文件:
<!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></head><body> <!--vue-ssr-outlet--></body></html>
可以發(fā)現(xiàn),在html的body里面添加了一段注釋,當(dāng)將vueServerRender編譯好的html傳到模板當(dāng)中之后這個(gè)地方將被替換成服務(wù)端預(yù)編譯的模板內(nèi)容,這樣也算是完成一個(gè)簡單的服務(wù)端預(yù)渲染了。雖然寫入的只是簡單的html渲染,沒有數(shù)據(jù)交互也沒有頁面交互,也算是一個(gè)不小的進(jìn)展了。
使用SSR搭建項(xiàng)目我們繼續(xù)延續(xù)上個(gè)項(xiàng)目繼續(xù)向下開發(fā),大家平時(shí)在使用vue-cli搭建項(xiàng)目的時(shí)候,都是在src文件夾下面進(jìn)行開發(fā)的,為了和vue項(xiàng)目結(jié)構(gòu)保持一致,同樣需要?jiǎng)?chuàng)建一個(gè)src文件夾,并在src文件夾創(chuàng)建conponents,router,utils,view,暫定項(xiàng)目結(jié)構(gòu)就這樣,隨著代碼的編寫會(huì)逐漸向項(xiàng)目里面添加內(nèi)容。
└─src| ├─components| ├─router| ├─utils| ├─view| └─app.js└─index.js
初始的目錄結(jié)構(gòu)已經(jīng)搭建好了之后,接下來需要繼續(xù)向下進(jìn)行,首先要做的就是要在router目錄中添加一個(gè)index.js文件,用來創(chuàng)建路由信息(在使用路由的時(shí)候一定要確保路由已經(jīng)安裝)。路由在項(xiàng)目中所起到的作用應(yīng)該是重要的,路由會(huì)通過路徑把頁面和組件之間建立聯(lián)系,并且一一的對應(yīng)起來,完成路由的渲染。
接下來在router下面的index.js文件中寫入如下配置:
const vueRouter = require("vue-router");const Vue = require("vue");Vue.use(vueRouter);modul.exports = () => { return new vueRouter({ mode:"history", routers:[ { path:"/", component:{ template:`<h1>這里是首頁<h1/>` }, name:"home" }, { path:"/about", component:{ template:`<h1>這里是關(guān)于頁<h1/>` }, name:"about" } ] })}上面的代碼中,仔細(xì)觀察的話,和平時(shí)在vue-cli中所導(dǎo)出的方式是不一樣的,這里采用了工廠方法,這里為什么要這樣?記得在雛形里面說過,為了保證用戶每次訪問都要生成一個(gè)新的路由,防止用戶與用戶之間相互影響,也就是說Vue實(shí)例是新的,我們的vue-router的實(shí)例也應(yīng)該保證它是一個(gè)全新的。
現(xiàn)在Vue實(shí)例和服務(wù)端混在一起,這樣對于項(xiàng)目的維護(hù)是很不好的,所以也需要把Vue從服務(wù)端單獨(dú)抽離出來,放到app.js中去。這里采用和router同樣的方式使用工廠方式,以保證每次被訪問都是一個(gè)全新的vue實(shí)例。在app.js導(dǎo)入剛剛寫好的路由,在每次觸發(fā)工廠的時(shí)候,創(chuàng)建一個(gè)新的路由實(shí)例,并綁定到vue實(shí)例里面,這樣用戶在訪問路徑的時(shí)候無論是vue實(shí)例還是router都是全新的了。
app.js:
const Vue = require("vue");const createRouter = require("./router")module.exports = (context) => { const router = createRouter(); return new Vue({ router, data:{ message:"Hello,Vue SSR!" }, template:` <div> <h1>{{message}}</h1> <ul> <li>, <router-link to="/">首頁<router-link/> </li> <li> <router-link to="/about">關(guān)于我<router-link/> </li> </ul> </div> <router-view></router-view> ` });}做完這些東西貌似好像就能用了一樣,但是還是不行,仔細(xì)想想好像忘了一些什么操作,剛剛把vue實(shí)例從index.js中抽離出來了,但是卻沒有在任何地方使用它,哈哈,好像是一件很尷尬的事情。
修改index.js文件:
const express = require("express");const vueApp = require("./src/app.js");const vueServerRender = require("vue-server-render").creteRender();const app = express();app.get('*',(request,respones) => { // 這里可以傳遞給vue實(shí)例一些參數(shù) let vm = vueApp({}) respones.status(200); respones.setHeader("Content-Type","text/html;charset-utf-8;"); vueServerRender.renderToString(vm).then((html) => { respones.end(html); }).catch(error => console.log(error));})app.listen(3000,() => { console.log("服務(wù)已啟動(dòng)")});準(zhǔn)備工作都已經(jīng)做好啦,完事具備只欠東風(fēng)啦。現(xiàn)在運(yùn)行一下npm start可以去頁面上看一下效果啦。看到頁面中已經(jīng)渲染出來了,但是好像是少了什么?雖然導(dǎo)航內(nèi)容已經(jīng)都顯示出來了,但是路由對應(yīng)的組件好像沒得渲染噻。具體是什么原因?qū)е碌哪兀瑅ue-router是由前端控制渲染的,當(dāng)訪問路由的時(shí)候其實(shí),在做首屏渲染的時(shí)候并沒有授權(quán)給服務(wù)端讓其去做渲染路由的工作。(⊙ 主站蜘蛛池模板: 昌宁县| 武邑县| 万荣县| 涡阳县| 佛学| 昌都县| 麟游县| 邵阳市| 漾濞| 榆树市| 微博| 大方县| 金昌市| 根河市| 武穴市| 本溪| 大石桥市| 淅川县| 绩溪县| 定襄县| 承德县| 庆云县| 阳曲县| 逊克县| 成安县| 榆中县| 梅河口市| 隆林| 磐安县| 纳雍县| 佛坪县| 东至县| 海晏县| 西昌市| 滕州市| 顺义区| 广东省| 工布江达县| 阿克苏市| 舟曲县| 安化县|