成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

手把手教你搭建SSR(vue/vue-cli + express)

liangdas / 3929人閱讀

摘要:最近簡(jiǎn)單的研究了一下,對(duì)已經(jīng)有了一個(gè)簡(jiǎn)單的認(rèn)知,主要應(yīng)用于單頁(yè)面應(yīng)用,是很不錯(cuò)的框架。創(chuàng)建好之后,在命令行直接輸入即可,當(dāng)控制臺(tái)顯示服務(wù)已啟動(dòng)則表示該服務(wù)已經(jīng)啟動(dòng)成功了。配置參數(shù)中有一項(xiàng)為這項(xiàng)配置的就是我們即將使用的模板。

最近簡(jiǎn)單的研究了一下SSR,對(duì)SSR已經(jīng)有了一個(gè)簡(jiǎn)單的認(rèn)知,主要應(yīng)用于單頁(yè)面應(yīng)用,NuxtSSR很不錯(cuò)的框架。也有過(guò)調(diào)研,簡(jiǎn)單的用了一下,感覺(jué)還是很不錯(cuò)。但是還是想知道若不依賴于框架又應(yīng)該如果處理SSR,研究一下做個(gè)筆記。

什么是SSR

Vue組件渲染為服務(wù)器端的HTML字符串,將他們直接發(fā)送到瀏覽器,最后將靜態(tài)標(biāo)記混合為客戶端上完全交互的應(yīng)用程序。

為什么要使用SSR

更好的SEO,搜索引擎爬蟲(chóng)爬取工具可以直接查看完全渲染的頁(yè)面

更寬的內(nèi)容達(dá)到時(shí)間(time-to-content),當(dāng)權(quán)請(qǐng)求頁(yè)面的時(shí)候,服務(wù)端渲染完數(shù)據(jù)之后,把渲染好的頁(yè)面直接發(fā)送給瀏覽器,并進(jìn)行渲染。瀏覽器只需要解析html不需要去解析js

SSR弊端

開(kāi)發(fā)條件受限,Vue組件的某些生命周期鉤子函數(shù)不能使用

開(kāi)發(fā)環(huán)境基于Node.js

會(huì)造成服務(wù)端更多的負(fù)載。在Node.js中渲染完整的應(yīng)用程序,顯然會(huì)比僅僅提供靜態(tài)文件server更加占用CPU資源,因此如果你在預(yù)料在高流量下使用,請(qǐng)準(zhǔn)備響應(yīng)的服務(wù)負(fù)載,并明智的采用緩存策略。

準(zhǔn)備工作

在正式開(kāi)始之前,在vue官網(wǎng)找到了一張這個(gè)圖片,圖中詳細(xì)的講述了vue中對(duì)ssr的實(shí)現(xiàn)思路。如下圖簡(jiǎn)單的說(shuō)一下。

下圖中很重要的一點(diǎn)就是webpack,在項(xiàng)目過(guò)程中會(huì)用到webpack的配置,從最左邊開(kāi)始就是我們所寫(xiě)入的源碼文件,所有的文件都有一個(gè)公共的入口文件app.js,然后就進(jìn)入了server-entry(服務(wù)端入口)和client-entry(客戶端入口),兩個(gè)入口文件都要經(jīng)過(guò)webpack,當(dāng)訪問(wèn)node端的時(shí)候,使用的是服務(wù)端渲染,在服務(wù)端渲染的時(shí)候,會(huì)生成一個(gè)server-Bender,最后通過(guò)server-Bundle可以渲染出HTML頁(yè)面,若在客戶端訪問(wèn)的時(shí)候則是使用客戶端渲染,通過(guò)client-Bundle在以后渲染出HTML頁(yè)面。so~通過(guò)這個(gè)圖可以很清晰的看出來(lái),接下來(lái)會(huì)用到兩個(gè)文件,一個(gè)server入口,一個(gè)client入口,最后由webpack生成server-Bundleclient-Bundle,最終當(dāng)去請(qǐng)求頁(yè)面的時(shí)候,node中的server-Bundle會(huì)生成HTML界面通過(guò)client-Bundle混合到html頁(yè)面中即可。

對(duì)于vue中使用ssr做了一些簡(jiǎn)單的了解之后,那么就開(kāi)始我們要做的第一步吧,首先要?jiǎng)?chuàng)建一個(gè)項(xiàng)目,創(chuàng)建一個(gè)文件夾,名字不重要,但是最好不要使用中文。

mkdir dome
cd dome
npm init

npm init命令用來(lái)初始化package.json文件:

{
  "name": "dome",   //  項(xiàng)目名稱
  "version": "1.0.0",   //  版本號(hào)
  "description": "",    //  描述
  "main": "index.js",   //  入口文件
  "scripts": {          //  命令行執(zhí)行命令 如:npm run test
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "Aaron",     //  作者
  "license": "ISC"      //  許可證
}

初始化完成之后接下來(lái)需要安裝,項(xiàng)目所需要依賴的包,所有依賴項(xiàng)如下:

npm install express --save-dev
npm install vue --save-dev
npm install vue-server-renderer --save-dev
npm install vue-router --save-dev

如上所有依賴項(xiàng)一一安裝即可,安裝完成之后就可以進(jìn)行下一步了。前面說(shuō)過(guò)SSR是服務(wù)端預(yù)渲染,所以當(dāng)然要?jiǎng)?chuàng)建一個(gè)Node服務(wù)來(lái)支撐。在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ù)開(kāi)發(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)成功了。接下來(lái)需要打開(kāi)瀏覽器看一下渲染的結(jié)果。在瀏覽器地址欄輸入locahost:3000則可以看到ok兩個(gè)字。

SSR渲染手動(dòng)搭建

前面的準(zhǔn)備工作已經(jīng)做好了,千萬(wàn)不要完了我們的主要目的不是為了渲染文字,主要的目標(biāo)是為了渲染*.vue文件或html所以。接下來(lái)就是做我們想要做的事情了。接下來(lái)就是要修改index.js文件,將之前安裝的vuevue-server-renderer引入進(jìn)來(lái)。

由于返回的不再是文字,而是html模板,所以我們要對(duì)響應(yīng)頭進(jìn)行更改,告訴瀏覽器我們渲染的是什么,否則瀏覽器是不知道該如何渲染服務(wù)器返回的數(shù)據(jù)。

index.js中引入了vue-server-renderer之后,在使用的時(shí)候,我們需要執(zhí)行一下vue-server-renderer其中的createRenderer方法,這個(gè)方法的作用就是會(huì)將vue的實(shí)例轉(zhuǎn)換成html的形式。

既然有了vue-server-renderer的方法,接下來(lái)就需要引入主角了vue,引入之后然后接著在下面創(chuàng)建一個(gè)vue實(shí)例,在web端使用vue的時(shí)候需要傳一些參數(shù)給Vue然而在服務(wù)端也是如此也可以傳遞一些參數(shù)給Vue實(shí)例,這個(gè)實(shí)例也就是后續(xù)添加的那些*.vue文件。為了防止用戶訪問(wèn)的時(shí)候頁(yè)面數(shù)據(jù)不會(huì)互相干擾,暫時(shí)需要把實(shí)例放到get請(qǐng)求中,每次有訪問(wèn)的時(shí)候就會(huì)創(chuàng)建一個(gè)新的實(shí)例,渲染新的模板。

creteRender方法能夠把vue的實(shí)例轉(zhuǎn)成html字符串傳遞到瀏覽器。那么接下來(lái)由應(yīng)該怎么做?在vueServerRender方法下面有一個(gè)renderToString方法,這個(gè)方法就可以幫助我們完成這步操作。這個(gè)方法接受的第一個(gè)參數(shù)是vue的實(shí)例,第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù),如果不想使用回調(diào)函數(shù)的話,這個(gè)方法也返回了一個(gè)Promise對(duì)象,當(dāng)方法執(zhí)行成功之后,會(huì)在then函數(shù)里面返回html結(jié)構(gòu)。

index.js改動(dòng)如下:

const express = require("express");
const Vue = require("vue");
const vueServerRender = require("vue-server-renderer").createRenderer();

const app = express();

app.get("*",(request,respones) => {
    const vueApp = new Vue({
        data:{
            message:"Hello,Vue SSR!"
        },
        template:`

{{message}}

` }); 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ù)訪問(wèn)一下locahost:3000,就會(huì)看到在服務(wù)端寫(xiě)入的HTML結(jié)構(gòu)了。這樣做好像給我們添加了大量的工作,到底與在web端直接使用有什么區(qū)別么?

接下來(lái)見(jiàn)證奇跡的時(shí)刻到了。在網(wǎng)頁(yè)中右鍵查看源代碼就會(huì)發(fā)現(xiàn)與之前的在web端使用的時(shí)候完全不同,可以看到渲染的模板了。如果細(xì)心的就會(huì)發(fā)現(xiàn)一件很有意思的事情,在h1標(biāo)簽上會(huì)有一個(gè)data-server-rendered=true這樣的屬性,這個(gè)可以告訴我們這個(gè)頁(yè)面是通過(guò)服務(wù)端渲染來(lái)做的。大家可以去其他各大網(wǎng)站看看哦。沒(méi)準(zhǔn)會(huì)有其他的收獲。

上面的案例中,雖然已經(jīng)實(shí)現(xiàn)了服務(wù)端預(yù)渲染,但是會(huì)有一個(gè)很大的缺陷,就是我們所渲染的這個(gè)網(wǎng)頁(yè)并不完整,沒(méi)有文檔聲明,head等等等,當(dāng)然可能會(huì)有一個(gè)其他的想法,就是使用es6的模板字符串做拼接就好了啊。確實(shí),這樣也是行的通的,但是這個(gè)仍是飲鴆止渴不能徹底的解決問(wèn)題,如果做過(guò)傳統(tǒng)MVC開(kāi)發(fā)的話,就應(yīng)該知道,MVC開(kāi)發(fā)模式全是基于模板的,現(xiàn)在這種與MVC有些相似的地方,同理也是可以使用模板的。在dome文件夾下創(chuàng)建index.html,并創(chuàng)建好HTML模板。

模板現(xiàn)在有了該如何使用?在createRenderer函數(shù)可以接收一個(gè)對(duì)象作為配置參數(shù)。配置參數(shù)中有一項(xiàng)為template,這項(xiàng)配置的就是我們即將使用的Html模板。這個(gè)接收的不是一個(gè)單純的路徑,我們需要使用fs模塊將html模板讀取出來(lái)。

其配置如下:

let path = require("path");
const vueServerRender = require("vue-server-renderer").createRenderer({
    template:require("fs").readFileSync(path.join(__dirname,"./index.html"),"utf-8")
});

現(xiàn)在模板已經(jīng)有了,在web端進(jìn)行開(kāi)發(fā)的時(shí)候,需要掛在一個(gè)el的掛載點(diǎn),這樣Vue才知道把這些template渲染在哪,服務(wù)端渲染也是如此,同樣也需要告訴Vuetemplate渲染到什么地方。接下來(lái)要做的事情就是在index.html中做手腳。來(lái)通知createRenderertemplate添加到什么地方。

更改index.html文件:







Document


    

可以發(fā)現(xiàn),在htmlbody里面添加了一段注釋,當(dāng)將vueServerRender編譯好的html傳到模板當(dāng)中之后這個(gè)地方將被替換成服務(wù)端預(yù)編譯的模板內(nèi)容,這樣也算是完成一個(gè)簡(jiǎn)單的服務(wù)端預(yù)渲染了。雖然寫(xiě)入的只是簡(jiǎn)單的html渲染,沒(méi)有數(shù)據(jù)交互也沒(méi)有頁(yè)面交互,也算是一個(gè)不小的進(jìn)展了。

使用SSR搭建項(xiàng)目我們繼續(xù)延續(xù)上個(gè)項(xiàng)目繼續(xù)向下開(kāi)發(fā),大家平時(shí)在使用vue-cli搭建項(xiàng)目的時(shí)候,都是在src文件夾下面進(jìn)行開(kāi)發(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)就這樣,隨著代碼的編寫(xiě)會(huì)逐漸向項(xiàng)目里面添加內(nèi)容。

└─src
|   ├─components
|   ├─router
|   ├─utils
|   ├─view
|   └─app.js
└─index.js

初始的目錄結(jié)構(gòu)已經(jīng)搭建好了之后,接下來(lái)需要繼續(xù)向下進(jìn)行,首先要做的就是要在router目錄中添加一個(gè)index.js文件,用來(lái)創(chuàng)建路由信息(在使用路由的時(shí)候一定要確保路由已經(jīng)安裝)。路由在項(xiàng)目中所起到的作用應(yīng)該是重要的,路由會(huì)通過(guò)路徑把頁(yè)面和組件之間建立聯(lián)系,并且一一的對(duì)應(yīng)起來(lái),完成路由的渲染。

接下來(lái)在router下面的index.js文件中寫(xiě)入如下配置:

const vueRouter = require("vue-router");
const Vue = require("vue");

Vue.use(vueRouter);

module.exports = () => {
    return new vueRouter({
        mode:"history",
        routes:[
            {
                path:"/",
                component:{
                    template:`

這里是首頁(yè)

` }, name:"home" }, { path:"/about", component:{ template:`

這里是關(guān)于我

` }, name:"about" } ] }) }

上面的代碼中,仔細(xì)觀察的話,和平時(shí)在vue-cli中所導(dǎo)出的方式是不一樣的,這里采用了工廠方法,這里為什么要這樣?記得在雛形里面說(shuō)過(guò),為了保證用戶每次訪問(wèn)都要生成一個(gè)新的路由,防止用戶與用戶之間相互影響,也就是說(shuō)Vue實(shí)例是新的,我們的vue-router的實(shí)例也應(yīng)該保證它是一個(gè)全新的。

現(xiàn)在Vue實(shí)例和服務(wù)端混在一起,這樣對(duì)于項(xiàng)目的維護(hù)是很不好的,所以也需要把Vue從服務(wù)端多帶帶抽離出來(lái),放到app.js中去。這里采用和router同樣的方式使用工廠方式,以保證每次被訪問(wèn)都是一個(gè)全新的vue實(shí)例。在app.js導(dǎo)入剛剛寫(xiě)好的路由,在每次觸發(fā)工廠的時(shí)候,創(chuàng)建一個(gè)新的路由實(shí)例,并綁定到vue實(shí)例里面,這樣用戶在訪問(wèn)路徑的時(shí)候無(wú)論是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:`
            

{{message}}

  • 首頁(yè)
  • 關(guān)于我
` }); }

做完這些東西貌似好像就能用了一樣,但是還是不行,仔細(xì)想想好像忘了一些什么操作,剛剛把vue實(shí)例從index.js中抽離出來(lái)了,但是卻沒(méi)有在任何地方使用它,哈哈,好像是一件很尷尬的事情。

修改index.js文件:

const express = require("express");
const vueApp = require("./src/app.js");
let path = require("path");
const vueServerRender = require("vue-server-renderer").createRenderer({
  template:require("fs").readFileSync(path.join(__dirname,"./index.html"),"utf-8")
});

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)啦?,F(xiàn)在運(yùn)行一下npm start可以去頁(yè)面上看一下效果啦。看到頁(yè)面中已經(jīng)渲染出來(lái)了,但是好像是少了什么?雖然導(dǎo)航內(nèi)容已經(jīng)都顯示出來(lái)了,但是路由對(duì)應(yīng)的組件好像沒(méi)得渲染噻。具體是什么原因?qū)е碌哪兀?b>vue-router是由前端控制渲染的,當(dāng)訪問(wèn)路由的時(shí)候其實(shí),在做首屏渲染的時(shí)候并沒(méi)有授權(quán)給服務(wù)端讓其去做渲染路由的工作。(⊙﹏⊙),是的我就是這么懶...

這個(gè)問(wèn)題解決方案也提供了相對(duì)應(yīng)的操作,不然就知道該怎么寫(xiě)下去了。既然在做渲染的時(shí)候分為服務(wù)端渲染和客戶端渲染兩種,那么我們就需要兩個(gè)入口文件,分別對(duì)應(yīng)的服務(wù)端渲染的入口文件,另個(gè)是客戶端渲染的入口文件。

src文件夾下面添加兩個(gè).js文件(當(dāng)然也可以放到其他地方,這里只是為了方便),entry-client.js這個(gè)文件用戶客戶端的入口文件,entry-server.js那么這個(gè)文件則就作為服務(wù)端的入口文件。既然入口文件已經(jīng)確定了,接下來(lái)就是要解決剛才的問(wèn)題了,首先解決的是服務(wù)端渲染,在服務(wù)端這里需要把用戶所訪問(wèn)的路徑傳遞給vue-router,如果不傳遞給vue-router的話,vue-router會(huì)一臉懵逼的看著你,你什么都不給我,我怎么知道渲染什么?

entry-server中需要做的事情就是需要把app.js導(dǎo)入進(jìn)來(lái),這里可以向上翻一下app.js中保存的是創(chuàng)建vue實(shí)例的方法。首先在里面寫(xiě)入一個(gè)函數(shù),至于為什么就不多說(shuō)了(同樣也是為了保證每次訪問(wèn)都有一個(gè)新的實(shí)例),這個(gè)函數(shù)接收一個(gè)參數(shù)([object]),由于這里考慮到可能會(huì)有異步操作(如懶加載),在這個(gè)函數(shù)中使用了Promise,在Promise中首先要拿到連個(gè)東西,不用猜也是能想到的,很重要的vue實(shí)例和router實(shí)例,so~但是在app中好像只導(dǎo)出了vue實(shí)例,還要根據(jù)當(dāng)前所需要的去更改app.js。

app.js:

const Vue = require("vue");
const createRouter = require("../router")

module.exports = (context) => {
    const router = createRouter();
    const app = new Vue({
        router,
        data:{
            message:"Hello,Vue SSR!"
        },
        template:`
            

{{message}}

  • 首頁(yè)
  • 關(guān)于我
` }); return { app, router } }

通過(guò)上面的改造之后,就可以在entry-server.js中輕松的拿到vuerouter的實(shí)例了,現(xiàn)在查看一下當(dāng)前entry-server.js中有那些可用參數(shù),vue,router,提及到的URL從哪里來(lái)?既然這個(gè)函數(shù)是給服務(wù)端使用的,那么當(dāng)服務(wù)端去執(zhí)行這個(gè)函數(shù)的時(shí)候,就可以通過(guò)參數(shù)形式傳遞進(jìn)來(lái),獲取到我們想要的參數(shù),我們假設(shè)這個(gè)參數(shù)叫做url,我們需要讓路由去做的就是跳轉(zhuǎn)到對(duì)應(yīng)的路由中(這一步很重要),然后再把對(duì)router的實(shí)例掛載到vue實(shí)例中,然后再把vue實(shí)例返回出去,供vueServerRender消費(fèi)。那么就需要導(dǎo)出這個(gè)函數(shù),以供服務(wù)端使用。

由于我們不能預(yù)測(cè)到用戶所訪問(wèn)的路由就是在vue-router中所配置的,所以需要在onReady的時(shí)候進(jìn)行處理,我們可以通過(guò)routergetMatchedComponents這個(gè)方法,獲取到我們所導(dǎo)入的組件,這些有個(gè)我們就可通過(guò)判斷組件對(duì)匹配結(jié)果進(jìn)行渲染。

entry-server.js

const createApp = require("./app.js");

module.exports = (context) => {
    return new Promise((reslove,reject) => {
        let {url} = context;
        let {app,router} = createApp(context);
        router.push(url);
        //  router回調(diào)函數(shù)
        //  當(dāng)所有異步請(qǐng)求完成之后就會(huì)觸發(fā)
        router.onReady(() => {
            let matchedComponents = router.getMatchedComponents();
            if(!matchedComponents.length){
                return reject({
                    code:404,
                });
            }
            reslove(app);
        },reject)
    })
}

既然實(shí)例又發(fā)生了變化,需要對(duì)應(yīng)發(fā)生變化的index.js同樣也需要做出對(duì)應(yīng)的改動(dòng)。把剛才的引入vue實(shí)例的路徑改為entey-server.js,由于這里返回的是一個(gè)Promise對(duì)象,這里使用async/await處理接收一下,并拿到vue實(shí)例。不要忘了把router所需要的url參數(shù)傳遞進(jìn)去。

index.js:

const express = require("express");
const App = require("./src/entry-server.js");
let path = require("path");
const vueServerRender = require("vue-server-renderer").createRenderer({
  template:require("fs").readFileSync(path.join(__dirname,"./index.html"),"utf-8")
});

const app = express();

app.get("*",async (request,respones) => {
    respones.status(200);
    respones.setHeader("Content-Type","text/html;charset-utf-8;");

    let {url} = request;
    //  這里可以傳遞給vue實(shí)例一些參數(shù)
    let vm = await App({url});
    vueServerRender.renderToString(vm).then((html) => {
        respones.end(html);
    }).catch(error => console.log(error));
})

app.listen(3000,() => {
    console.log("服務(wù)已啟動(dòng)")
});

這下子就完成了,啟動(dòng)項(xiàng)目吧,當(dāng)訪問(wèn)根路徑的時(shí)候,就會(huì)看到剛才缺少的組件也已經(jīng)渲染出來(lái)了,當(dāng)然我們也可以切換路由,也是沒(méi)有問(wèn)題的。大功告成。。。好像并沒(méi)有emmmmmmmmm,為什么,細(xì)心的話應(yīng)該會(huì)發(fā)現(xiàn),當(dāng)我們切換路由的時(shí)候,地址欄旁邊的刷新按鈕一直在閃動(dòng),這也就是說(shuō),我們所做出來(lái)的并不是一個(gè)單頁(yè)應(yīng)用(手動(dòng)笑哭),出現(xiàn)這樣的問(wèn)題也是難怪的,畢竟我們沒(méi)有配置前端路由,我們把所有路由的控制權(quán)都交給了服務(wù)端,每次訪問(wèn)一個(gè)路由的時(shí)候,都會(huì)向服務(wù)端發(fā)送一個(gè)請(qǐng)求,返回路由對(duì)應(yīng)的頁(yè)面。想要解決這個(gè)問(wèn)題,當(dāng)處于前端的時(shí)候我們需要讓服務(wù)端把路由的控制權(quán)交還給前端路由,讓前端去控制路由的跳轉(zhuǎn)。

之前在src文件夾下面添加了兩個(gè)文件,只用到了服務(wù)端的文件,為了在客戶端能夠交還路由控制權(quán),要對(duì)web端路由進(jìn)行配置。由于在客戶端在使用vue的時(shí)候需要掛載一個(gè)document,因?yàn)?b>vue的實(shí)例已經(jīng)創(chuàng)建完成了,所以,這里需要使用$mount這個(gè)鉤子函數(shù),來(lái)完成客戶端的掛載。同樣為了解決懶加載這種類似的問(wèn)題so~同樣需要使用onReady里進(jìn)行路由的處理,只有當(dāng)vue-router加載完成以后再去掛載。

在客戶端是使用的時(shí)候很簡(jiǎn)單,只需要把路由掛載到app里面就可以了。

entry-client.js

const createApp = require("./app.js");
let {app,router} = createApp({});

router.onReady(() => {
    app.$mount("#app")
});

整個(gè)項(xiàng)目的雛形也就這樣了,由于服務(wù)端把路由控制權(quán)交還給客戶端,需要復(fù)雜的webpack配置,so~不再贅述了,下面直接使用vue-cli繼續(xù)(做的是使用需要用到上面的代碼)。

vue-cli項(xiàng)目搭建

在做準(zhǔn)備工作的時(shí)候簡(jiǎn)單講述了vue中使用ssr的運(yùn)行思路,里面提及了一個(gè)很重要的webpack,因此這里需要借助vue-cli腳手架,直接更改原有的webpack就可以了,這樣會(huì)方便很多。

這里建議大家返回頂部再次看一下vue服務(wù)端渲染的流程,在介紹中的client-bundleserver-bundle,,所以需要構(gòu)建兩個(gè)配置,分別是服務(wù)端配置和客戶端的配置。

如想要實(shí)現(xiàn)服務(wù)端渲染需要對(duì)vue-cli中個(gè)js文件中的配置進(jìn)行修改。以下只展示更改部分的代碼,不展示全部。

文件分別是:

webpack.server.conf.js - 服務(wù)端webpack配置

dev-server.js - 獲取服務(wù)端bundle

server.js - 創(chuàng)建后端服務(wù)

webpack.dev.conf.js - 客戶端的bundle

webpack.base.conf - 修改入口文件

客戶端配置

客戶端生成一份客戶端構(gòu)建清單,記錄客戶端的資源,最終會(huì)將客戶端構(gòu)建清單中記錄的文件,注入到執(zhí)行的執(zhí)行的模板中,這個(gè)清單與服務(wù)端類似,同樣也會(huì)生成一份json文件,這個(gè)文件的名字是vue-ssr-client-manifest.json(項(xiàng)目啟動(dòng)以后可以通過(guò)地址/文件名訪問(wèn)到),當(dāng)然必不可少的是,同樣也需要引入一個(gè)叫做vue-server-renderer/client-plugin模塊,作為webpack的插件供其使用。

首先要安裝一下vue-server-renderer這個(gè)模塊,這個(gè)是整個(gè)服務(wù)端渲染的核心,沒(méi)有整個(gè)ssr是沒(méi)有任何靈魂的。

npm install vue-server-renderer -S

安裝完成之后,首先要找到webpack.dev.conf.js,首先要對(duì)其進(jìn)行相關(guān)配置。

webpack.dev.conf.js

//  添加引入  vue-server-render/client-plugin  模塊
const vueSSRClientPlugin = require("vue-server-renderer/client-plugin");

const devWebpackConfig = merge(baseWebpackConfig,{
    plugins:[
        new vueSSRClientPlugin()
    ] 
});

添加了這個(gè)配置以后,重新啟動(dòng)項(xiàng)目通過(guò)地址就可以訪問(wèn)到vue-ssr-client-manifest.json(http://localhost:8080/vue-ssr-client-manifest.json),頁(yè)面中出現(xiàn)的內(nèi)容就是所需要的client-bundle。

服務(wù)端配置

服務(wù)端會(huì)默認(rèn)生成一個(gè)vue-ssr-server-bundle.json文件,在文件中會(huì)記錄整個(gè)服務(wù)端整個(gè)輸出,怎么才能生成這個(gè)文件呢?要在這個(gè)json文件,必須要引入vue-server-renderer/server-plugin,并將其作為webpack的插件。

在開(kāi)始服務(wù)端配置之前,需要在src文件夾下面創(chuàng)建三個(gè)文件,app.jsentry-client.js,entry-server.js,創(chuàng)建完成之后需要對(duì)其寫(xiě)入相關(guān)代碼。

src/router/index.js

import vueRouter from "vue-router";
import Vue from "vue";
import HelloWorld from "@/components/HelloWorld";

Vue.use(vueRouter);
export default () => {
    return new vueRouter({
        mode:"history",
        routes:[
            {
                path:"/",
                component:HelloWorld,
                name:"HelloWorld"
            }
        ]
    })
}

app.js

import Vue from "vue";
import createRouter from "./router";
import App from "./App.vue";

export default (context) => {
    const router = createRouter();
    const app = new Vue({
        router,
        components: { App },
        template: ""
    });
    return {
        app,
        router
    }
}

entry-server.js

import createApp from "./app.js";

export default (context) => {
    return new Promise((reslove,reject) => {
        let {url} = context;
        let {app,router} = createApp(context);
        router.push(url);
        router.onReady(() => {
            let matchedComponents = router.getMatchedComponents();
            if(!matchedComponents.length){
                return reject({
                    code:404,
                });
            }
            reslove(app);
        },reject)
    })
}

entry-client.js

import createApp from "./app.js";
let {app,router} = createApp();

router.onReady(() => {
    app.$mount("#app");
});

webpack.base.conf.js

module.exports = {
    entry:{
        app:"./src/entry-client.js"
    },
    output:{
        publicPath:"http://localhost:8080/"
    }
};

webpack.server.conf.js(手動(dòng)創(chuàng)建)

const webpack = require("webpack");
const merge = require("webpack-merge");
const base = require("./webpack.base.conf");
//  手動(dòng)安裝
//  在服務(wù)端渲染中,所需要的文件都是使用require引入,不需要把node_modules文件打包
const webapckNodeExternals = require("webpack-node-externals");


const vueSSRServerPlugin = require("vue-server-renderer/server-plugin");

module.exports = merge(base,{
    //  告知webpack,需要在node端運(yùn)行
    target:"node",
    entry:"./src/entry-server.js",
    devtool:"source-map",
    output:{
        filename:"server-buldle.js",
        libraryTarget: "commonjs2"
    },
    externals:[
        webapckNodeExternals()
    ],
    plugins:[
        new webpack.DefinePlugin({
            "process.env.NODE_ENV":""devlopment"",
            "process.ent.VUE_ENV": ""server""
        }),
        new vueSSRServerPlugin()
    ]
});

dev-server.js(手動(dòng)創(chuàng)建)

const serverConf = require("./webpack.server.conf");
const webpack = require("webpack");
const fs = require("fs");
const path = require("path");
//  讀取內(nèi)存中的.json文件
//  這個(gè)模塊需要手動(dòng)安裝
const Mfs = require("memory-fs");
const axios = require("axios");

module.exports = (cb) => {
    const webpackComplier = webpack(serverConf);
    var mfs = new Mfs();
    
    webpackComplier.outputFileSystem = mfs;
    
    webpackComplier.watch({},async (error,stats) => {
        if(error) return console.log(error);
        stats = stats.toJson();
        stats.errors.forEach(error => console.log(error));
        stats.warnings.forEach(warning => console.log(warning));
        //  獲取server bundle的json文件
        let serverBundlePath = path.join(serverConf.output.path,"vue-ssr-server-bundle.json");
        let serverBundle = JSON.parse(mfs.readFileSync(serverBundlePath,"utf-8"));
        //  獲取client bundle的json文件
        let clientBundle = await axios.get("http://localhost:8080/vue-ssr-client-manifest.json");
        //  獲取模板
        let template = fs.readFileSync(path.join(__dirname,"..","index.html"),"utf-8");
        cb && cb(serverBundle,clientBundle,template);
    })
};

根目錄/server.js(手動(dòng)創(chuàng)建)

const devServer = require("./build/dev-server.js");
const express = require("express");
const app = express();
const vueRender = require("vue-server-renderer");

app.get("*",(request,respones) => {
    respones.status(200);
    respones.setHeader("Content-Type","text/html;charset-utf-8;");
    devServer((serverBundle,clientBundle,template) => {
        let render = vueRender.createBundleRenderer(serverBundle,{
            template,
            clientManifest:clientBundle.data,
            //  每次創(chuàng)建一個(gè)獨(dú)立的上下文
            renInNewContext:false
        }); 
        render.renderToString({
            url:request.url
        }).then((html) => {
            respones.end(html);
        }).catch(error => console.log(error));
    });
})

app.listen(5000,() => {
    console.log("服務(wù)已啟動(dòng)")
});

index.html







Document


    

以上就是所有要更改和添加的配置項(xiàng),配置完所有地方就可以完成服務(wù)端渲染。此時(shí)需要在package.json中的sctipt中添加啟動(dòng)項(xiàng):http:node server.js,就可以正常運(yùn)行項(xiàng)目了。注意一定要去訪問(wèn)服務(wù)端設(shè)置的端口,同時(shí)要保證你的客戶端也是在線的。

總結(jié)

這篇博客耗時(shí)3天才完成,可能讀起來(lái)會(huì)很費(fèi)時(shí)間,但是卻有很大的幫助,希望大家能夠好好閱讀這篇文章,對(duì)大家有所幫助。

感謝大家花費(fèi)很長(zhǎng)時(shí)間來(lái)閱讀這篇文章,若文章中有錯(cuò)誤灰常感謝大家提出指正,我會(huì)盡快做出修改的。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/104641.html

相關(guān)文章

  • Vue+ElementUI: 把手教你做一個(gè)audio組件

    摘要:不顯示下載不顯示靜音不顯示音量條不顯示進(jìn)度條只能播放一個(gè)不要快進(jìn)按鈕例如父組件這樣回雪月花青春一點(diǎn)點(diǎn)語(yǔ)法大多數(shù)時(shí)候,我們希望頁(yè)面上播放一個(gè)音頻時(shí),其他音頻可以暫停??梢园岩粋€(gè)類數(shù)組轉(zhuǎn)化成數(shù)組,這個(gè)是我常用的。 showImg(https://segmentfault.com/img/remote/1460000016177005?w=619&h=343); 目的 本項(xiàng)目的目的是教你如...

    U2FsdGVkX1x 評(píng)論0 收藏0
  • 無(wú)痛學(xué)會(huì)各種 2 的 Vue2+Vuex2+Webpack2 前后端同構(gòu)渲染

    摘要:它會(huì)檢測(cè)出最大靜態(tài)子樹(shù)就是不需要?jiǎng)討B(tài)性的子樹(shù)并且從渲染函數(shù)中萃取出來(lái)。這樣在每次重渲染的時(shí)候,它就會(huì)直接重用完全相同的同時(shí)跳過(guò)比對(duì)。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個(gè)鏈接~手把手教你從零寫(xiě)一個(gè)簡(jiǎn)單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非?;鸬膙ue2前端框架的特點(diǎn)和vue2+vuex2+we...

    fish 評(píng)論0 收藏0
  • 無(wú)痛學(xué)會(huì)各種 2 的 Vue2+Vuex2+Webpack2 前后端同構(gòu)渲染

    摘要:它會(huì)檢測(cè)出最大靜態(tài)子樹(shù)就是不需要?jiǎng)討B(tài)性的子樹(shù)并且從渲染函數(shù)中萃取出來(lái)。這樣在每次重渲染的時(shí)候,它就會(huì)直接重用完全相同的同時(shí)跳過(guò)比對(duì)。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個(gè)鏈接~手把手教你從零寫(xiě)一個(gè)簡(jiǎn)單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非?;鸬膙ue2前端框架的特點(diǎn)和vue2+vuex2+we...

    30e8336b8229 評(píng)論0 收藏0
  • 無(wú)痛學(xué)會(huì)各種 2 的 Vue2+Vuex2+Webpack2 前后端同構(gòu)渲染

    摘要:它會(huì)檢測(cè)出最大靜態(tài)子樹(shù)就是不需要?jiǎng)討B(tài)性的子樹(shù)并且從渲染函數(shù)中萃取出來(lái)。這樣在每次重渲染的時(shí)候,它就會(huì)直接重用完全相同的同時(shí)跳過(guò)比對(duì)。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個(gè)鏈接~手把手教你從零寫(xiě)一個(gè)簡(jiǎn)單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非?;鸬膙ue2前端框架的特點(diǎn)和vue2+vuex2+we...

    Pluser 評(píng)論0 收藏0
  • 后端API從入門(mén)到放棄指北

    摘要:菜鳥(niǎo)教程框架中文手冊(cè)入門(mén)目標(biāo)使用搭建通過(guò)對(duì)數(shù)據(jù)增刪查改沒(méi)了純粹占行用的拜 后端API入門(mén)學(xué)習(xí)指北 了解一下一下概念. RESTful API標(biāo)準(zhǔn)] 所有的API都遵循[RESTful API標(biāo)準(zhǔn)]. 建議大家都簡(jiǎn)單了解一下HTTP協(xié)議和RESTful API相關(guān)資料. 阮一峰:理解RESTful架構(gòu) 阮一峰:RESTful API 設(shè)計(jì)指南 RESTful API指南 依賴注入 D...

    Jeffrrey 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<