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

最近簡單的研究了一下SSR,對SSR已經有了一個簡單的認知,主要應用於單頁面應用,NuxtSSR很不錯的框架。也有過調研,簡單的用了一下,感受仍是很不錯。可是仍是想知道若不依賴於框架又應該若是處理SSR,研究一下作個筆記。html

什麼是SSR

Vue組件渲染爲服務器端的HTML字符串,將他們直接發送到瀏覽器,最後將靜態標記混合爲客戶端上徹底交互的應用程序。前端

爲何要使用SSR

  1. 更好的SEO,搜索引擎爬蟲爬取工具能夠直接查看徹底渲染的頁面
  2. 更寬的內容達到時間(time-to-content),當權請求頁面的時候,服務端渲染完數據以後,把渲染好的頁面直接發送給瀏覽器,並進行渲染。瀏覽器只須要解析html不須要去解析js

SSR弊端

  1. 開發條件受限,Vue組件的某些生命週期鉤子函數不能使用
  2. 開發環境基於Node.js
  3. 會形成服務端更多的負載。在Node.js中渲染完整的應用程序,顯然會比僅僅提供靜態文件server更加佔用CPU資源,所以若是你在預料在高流量下使用,請準備響應的服務負載,並明智的採用緩存策略。

準備工做

在正式開始以前,在vue官網找到了一張這個圖片,圖中詳細的講述了vue中對ssr的實現思路。以下圖簡單的說一下。vue

下圖中很重要的一點就是webpack,在項目過程當中會用到webpack的配置,從最左邊開始就是咱們所寫入的源碼文件,全部的文件都有一個公共的入口文件app.js,而後就進入了server-entry(服務端入口)和client-entry(客戶端入口),兩個入口文件都要通過webpack,當訪問node端的時候,使用的是服務端渲染,在服務端渲染的時候,會生成一個server-Bender,最後經過server-Bundle能夠渲染出HTML頁面,若在客戶端訪問的時候則是使用客戶端渲染,經過client-Bundle在之後渲染出HTML頁面。so~經過這個圖能夠很清晰的看出來,接下來會用到兩個文件,一個server入口,一個client入口,最後由webpack生成server-Bundleclient-Bundle,最終當去請求頁面的時候,node中的server-Bundle會生成HTML界面經過client-Bundle混合到html頁面中便可。node

blogssr1.png

對於vue中使用ssr作了一些簡單的瞭解以後,那麼就開始咱們要作的第一步吧,首先要建立一個項目,建立一個文件夾,名字不重要,可是最好不要使用中文。webpack

mkdir dome
cd dome
npm init

npm init命令用來初始化package.json文件:ios

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

初始化完成以後接下來須要安裝,項目所須要依賴的包,全部依賴項以下:es6

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

如上全部依賴項一一安裝便可,安裝完成以後就能夠進行下一步了。前面說過SSR是服務端預渲染,因此固然要建立一個Node服務來支撐。在dome文件夾下面建立一個index.js文件,並使用express建立一個服務。web

代碼以下:vue-router

const express = require("express");
const app = express();

app.get('*',(request,respones) => {
    respones.end("ok");
})

app.listen(3000,() => {
    console.log("服務已啓動")
});

完成上述代碼以後,爲了方便咱們須要在package.json添加一個命令,方便後續開發啓動項目。vue-cli

{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  }
}

建立好以後,在命令行直接輸入npm start便可,當控制檯顯示服務已啓動則表示該服務已經啓動成功了。接下來須要打開瀏覽器看一下渲染的結果。在瀏覽器地址欄輸入locahost:3000則能夠看到ok兩個字。

SSR渲染手動搭建

前面的準備工做已經作好了,千萬不要完了咱們的主要目的不是爲了渲染文字,主要的目標是爲了渲染*.vue文件或html因此。接下來就是作咱們想要作的事情了。接下來就是要修改index.js文件,將以前安裝的vuevue-server-renderer引入進來。

因爲返回的再也不是文字,而是html模板,因此咱們要對響應頭進行更改,告訴瀏覽器咱們渲染的是什麼,不然瀏覽器是不知道該如何渲染服務器返回的數據。

index.js中引入了vue-server-renderer以後,在使用的時候,咱們須要執行一下vue-server-renderer其中的createRenderer方法,這個方法的做用就是會將vue的實例轉換成html的形式。

既然有了vue-server-renderer的方法,接下來就須要引入主角了vue,引入以後而後接着在下面建立一個vue實例,在web端使用vue的時候須要傳一些參數給Vue然而在服務端也是如此也能夠傳遞一些參數給Vue實例,這個實例也就是後續添加的那些*.vue文件。爲了防止用戶訪問的時候頁面數據不會互相干擾,暫時須要把實例放到get請求中,每次有訪問的時候就會建立一個新的實例,渲染新的模板。

creteRender方法可以把vue的實例轉成html字符串傳遞到瀏覽器。那麼接下來由應該怎麼作?在vueServerRender方法下面有一個renderToString方法,這個方法就能夠幫助咱們完成這步操做。這個方法接受的第一個參數是vue的實例,第二個參數是一個回調函數,若是不想使用回調函數的話,這個方法也返回了一個Promise對象,當方法執行成功以後,會在then函數裏面返回html結構。

index.js改動以下:

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:`<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("服務已啓動")
});

上述操做完成以後,必定要記得保存,而後重啓服務器,繼續訪問一下locahost:3000,就會看到在服務端寫入的HTML結構了。這樣作好像給咱們添加了大量的工做,到底與在web端直接使用有什麼區別麼?

接下來見證奇蹟的時刻到了。在網頁中右鍵查看源代碼就會發現與以前的在web端使用的時候徹底不一樣,能夠看到渲染的模板了。若是細心的就會發現一件頗有意思的事情,在h1標籤上會有一個data-server-rendered=true這樣的屬性,這個能夠告訴咱們這個頁面是經過服務端渲染來作的。你們能夠去其餘各大網站看看哦。沒準會有其餘的收穫。

上面的案例中,雖然已經實現了服務端預渲染,可是會有一個很大的缺陷,就是咱們所渲染的這個網頁並不完整,沒有文檔聲明,head等等等,固然可能會有一個其餘的想法,就是使用es6的模板字符串作拼接就行了啊。確實,這樣也是行的通的,可是這個還是飲鴆止渴不能完全的解決問題,若是作過傳統MVC開發的話,就應該知道,MVC開發模式全是基於模板的,如今這種與MVC有些類似的地方,同理也是可使用模板的。在dome文件夾下建立index.html,並建立好HTML模板。

模板如今有了該如何使用?在createRenderer函數能夠接收一個對象做爲配置參數。配置參數中有一項爲template,這項配置的就是咱們即將使用的Html模板。這個接收的不是一個單純的路徑,咱們須要使用fs模塊將html模板讀取出來。

其配置以下:

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

如今模板已經有了,在web端進行開發的時候,須要掛在一個el的掛載點,這樣Vue才知道把這些template渲染在哪,服務端渲染也是如此,一樣也須要告訴Vuetemplate渲染到什麼地方。接下來要作的事情就是在index.html中作手腳。來通知createRenderertemplate添加到什麼地方。

更改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>

能夠發現,在htmlbody裏面添加了一段註釋,當將vueServerRender編譯好的html傳到模板當中以後這個地方將被替換成服務端預編譯的模板內容,這樣也算是完成一個簡單的服務端預渲染了。雖然寫入的只是簡單的html渲染,沒有數據交互也沒有頁面交互,也算是一個不小的進展了。

使用SSR搭建項目咱們繼續延續上個項目繼續向下開發,你們平時在使用vue-cli搭建項目的時候,都是在src文件夾下面進行開發的,爲了和vue項目結構保持一致,一樣須要建立一個src文件夾,並在src文件夾建立conponents,router,utils,view,暫定項目結構就這樣,隨着代碼的編寫會逐漸向項目裏面添加內容。

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

初始的目錄結構已經搭建好了以後,接下來須要繼續向下進行,首先要作的就是要在router目錄中添加一個index.js文件,用來建立路由信息(在使用路由的時候必定要確保路由已經安裝)。路由在項目中所起到的做用應該是重要的,路由會經過路徑把頁面和組件之間創建聯繫,而且一一的對應起來,完成路由的渲染。

接下來在router下面的index.js文件中寫入以下配置:

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

Vue.use(vueRouter);

module.exports = () => {
    return new vueRouter({
        mode:"history",
        routes:[
            {
                path:"/",
                component:{
                    template:`<h1>這裏是首頁</h1>`
                },
                name:"home"
            },
            {
                path:"/about",
                component:{
                    template:`<h1>這裏是關於我</h1>`
                },
                name:"about"
            }
        ]
    })
}

上面的代碼中,仔細觀察的話,和平時在vue-cli中所導出的方式是不同的,這裏採用了工廠方法,這裏爲何要這樣?記得在雛形裏面說過,爲了保證用戶每次訪問都要生成一個新的路由,防止用戶與用戶之間相互影響,也就是說Vue實例是新的,咱們的vue-router的實例也應該保證它是一個全新的。

如今Vue實例和服務端混在一塊兒,這樣對於項目的維護是很很差的,因此也須要把Vue從服務端單獨抽離出來,放到app.js中去。這裏採用和router一樣的方式使用工廠方式,以保證每次被訪問都是一個全新的vue實例。在app.js導入剛剛寫好的路由,在每次觸發工廠的時候,建立一個新的路由實例,並綁定到vue實例裏面,這樣用戶在訪問路徑的時候不管是vue實例仍是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>
                <div>
                    <h1>{{message}}</h1>
                    <ul>
                        <li>
                            <router-link to="/">首頁</router-link>
                        </li>
                        <li>
                            <router-link to="/about">關於我</router-link>
                        </li>
                    </ul>
                </div>
                <router-view></router-view>
            </div>
        ` 
    });
}

作完這些東西貌似好像就能用了同樣,可是仍是不行,仔細想一想好像忘了一些什麼操做,剛剛把vue實例從index.js中抽離出來了,可是卻沒有在任何地方使用它,哈哈,好像是一件很尷尬的事情。

修改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實例一些參數
    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("服務已啓動")
});

準備工做都已經作好啦,完事具有隻欠東風啦。如今運行一下npm start能夠去頁面上看一下效果啦。看到頁面中已經渲染出來了,可是好像是少了什麼?雖然導航內容已經都顯示出來了,可是路由對應的組件好像沒得渲染噻。具體是什麼緣由致使的呢,vue-router是由前端控制渲染的,當訪問路由的時候其實,在作首屏渲染的時候並無受權給服務端讓其去作渲染路由的工做。(⊙﹏⊙),是的我就是這麼懶...

這個問題解決方案也提供了相對應的操做,否則就知道該怎麼寫下去了。既然在作渲染的時候分爲服務端渲染和客戶端渲染兩種,那麼咱們就須要兩個入口文件,分別對應的服務端渲染的入口文件,另個是客戶端渲染的入口文件。

src文件夾下面添加兩個.js文件(固然也能夠放到其餘地方,這裏只是爲了方便),entry-client.js這個文件用戶客戶端的入口文件,entry-server.js那麼這個文件則就做爲服務端的入口文件。既然入口文件已經肯定了,接下來就是要解決剛纔的問題了,首先解決的是服務端渲染,在服務端這裏須要把用戶所訪問的路徑傳遞給vue-router,若是不傳遞給vue-router的話,vue-router會一臉懵逼的看着你,你什麼都不給我,我怎麼知道渲染什麼?

entry-server中須要作的事情就是須要把app.js導入進來,這裏能夠向上翻一下app.js中保存的是建立vue實例的方法。首先在裏面寫入一個函數,至於爲何就很少說了(一樣也是爲了保證每次訪問都有一個新的實例),這個函數接收一個參數([object]),因爲這裏考慮到可能會有異步操做(如懶加載),在這個函數中使用了Promise,在Promise中首先要拿到連個東西,不用猜也是能想到的,很重要的vue實例和router實例,so~可是在app中好像只導出了vue實例,還要根據當前所須要的去更改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:`
            <div>
                <div>
                    <h1>{{message}}</h1>
                    <ul>
                        <li>
                            <router-link to="/">首頁</router-link>
                        </li>
                        <li>
                            <router-link to="/about">關於我</router-link>
                        </li>
                    </ul>
                </div>
                <router-view></router-view>
            </div>
        ` 
    });
    return {
        app,
        router
    }
}

經過上面的改造以後,就能夠在entry-server.js中輕鬆的拿到vuerouter的實例了,如今查看一下當前entry-server.js中有那些可用參數,vue,router,說起到的URL從哪裏來?既然這個函數是給服務端使用的,那麼當服務端去執行這個函數的時候,就能夠經過參數形式傳遞進來,獲取到咱們想要的參數,咱們假設這個參數叫作url,咱們須要讓路由去作的就是跳轉到對應的路由中(這一步很重要),而後再把對router的實例掛載到vue實例中,而後再把vue實例返回出去,供vueServerRender消費。那麼就須要導出這個函數,以供服務端使用。

因爲咱們不能預測到用戶所訪問的路由就是在vue-router中所配置的,因此須要在onReady的時候進行處理,咱們能夠經過routergetMatchedComponents這個方法,獲取到咱們所導入的組件,這些有個咱們就可經過判斷組件對匹配結果進行渲染。

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回調函數
        //  當全部異步請求完成以後就會觸發
        router.onReady(() => {
            let matchedComponents = router.getMatchedComponents();
            if(!matchedComponents.length){
                return reject({
                    code:404,
                });
            }
            reslove(app);
        },reject)
    })
}

既然實例又發生了變化,須要對應發生變化的index.js一樣也須要作出對應的改動。把剛纔的引入vue實例的路徑改成entey-server.js,因爲這裏返回的是一個Promise對象,這裏使用async/await處理接收一下,並拿到vue實例。不要忘了把router所須要的url參數傳遞進去。

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實例一些參數
    let vm = await App({url});
    vueServerRender.renderToString(vm).then((html) => {
        respones.end(html);
    }).catch(error => console.log(error));
})

app.listen(3000,() => {
    console.log("服務已啓動")
});

這下子就完成了,啓動項目吧,當訪問根路徑的時候,就會看到剛纔缺乏的組件也已經渲染出來了,固然咱們也能夠切換路由,也是沒有問題的。大功告成。。。好像並無emmmmmmmmm,爲何,細心的話應該會發現,當咱們切換路由的時候,地址欄旁邊的刷新按鈕一直在閃動,這也就是說,咱們所作出來的並非一個單頁應用(手動笑哭),出現這樣的問題也是難怪的,畢竟咱們沒有配置前端路由,咱們把全部路由的控制權都交給了服務端,每次訪問一個路由的時候,都會向服務端發送一個請求,返回路由對應的頁面。想要解決這個問題,當處於前端的時候咱們須要讓服務端把路由的控制權交還給前端路由,讓前端去控制路由的跳轉。

以前在src文件夾下面添加了兩個文件,只用到了服務端的文件,爲了在客戶端可以交還路由控制權,要對web端路由進行配置。因爲在客戶端在使用vue的時候須要掛載一個document,由於vue的實例已經建立完成了,因此,這裏須要使用$mount這個鉤子函數,來完成客戶端的掛載。一樣爲了解決懶加載這種相似的問題so~一樣須要使用onReady裏進行路由的處理,只有當vue-router加載完成之後再去掛載。

在客戶端是使用的時候很簡單,只須要把路由掛載到app裏面就能夠了。

entry-client.js

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

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

整個項目的雛形也就這樣了,因爲服務端把路由控制權交還給客戶端,須要複雜的webpack配置,so~再也不贅述了,下面直接使用vue-cli繼續(作的是使用須要用到上面的代碼)。

vue-cli項目搭建

在作準備工做的時候簡單講述了vue中使用ssr的運行思路,裏面說起了一個很重要的webpack,所以這裏須要藉助vue-cli腳手架,直接更改原有的webpack就能夠了,這樣會方便不少。

這裏建議你們返回頂部再次看一下vue服務端渲染的流程,在介紹中的client-bundleserver-bundle,,因此須要構建兩個配置,分別是服務端配置和客戶端的配置。

如想要實現服務端渲染須要對vue-cli中個js文件中的配置進行修改。如下只展現更改部分的代碼,不展現所有。

文件分別是:

  1. webpack.server.conf.js - 服務端webpack配置
  2. dev-server.js - 獲取服務端bundle
  3. server.js - 建立後端服務
  4. webpack.dev.conf.js - 客戶端的bundle
  5. webpack.base.conf - 修改入口文件

客戶端配置

客戶端生成一份客戶端構建清單,記錄客戶端的資源,最終會將客戶端構建清單中記錄的文件,注入到執行的執行的模板中,這個清單與服務端相似,一樣也會生成一份json文件,這個文件的名字是vue-ssr-client-manifest.json(項目啓動之後能夠經過地址/文件名訪問到),固然必不可少的是,一樣也須要引入一個叫作vue-server-renderer/client-plugin模塊,做爲webpack的插件供其使用。

首先要安裝一下vue-server-renderer這個模塊,這個是整個服務端渲染的核心,沒有整個ssr是沒有任何靈魂的。

npm install vue-server-renderer -S

安裝完成以後,首先要找到webpack.dev.conf.js,首先要對其進行相關配置。

webpack.dev.conf.js

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

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

添加了這個配置之後,從新啓動項目經過地址就能夠訪問到vue-ssr-client-manifest.jsonhttp://localhost:8080/vue-ssr-client-manifest.json),頁面中出現的內容就是所須要的client-bundle

服務端配置

服務端會默認生成一個vue-ssr-server-bundle.json文件,在文件中會記錄整個服務端整個輸出,怎麼才能生成這個文件呢?要在這個json文件,必需要引入vue-server-renderer/server-plugin,並將其做爲webpack的插件。

在開始服務端配置以前,須要在src文件夾下面建立三個文件,app.jsentry-client.jsentry-server.js,建立完成以後須要對其寫入相關代碼。

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: '<App/>'
    });
    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(手動建立)

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


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

module.exports = merge(base,{
    //  告知webpack,須要在node端運行
    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(手動建立)

const serverConf = require("./webpack.server.conf");
const webpack = require("webpack");
const fs = require("fs");
const path = require("path");
//  讀取內存中的.json文件
//  這個模塊須要手動安裝
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(手動建立)

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,
            //  每次建立一個獨立的上下文
            renInNewContext:false
        }); 
        render.renderToString({
            url:request.url
        }).then((html) => {
            respones.end(html);
        }).catch(error => console.log(error));
    });
})

app.listen(5000,() => {
    console.log("服務已啓動")
});

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>
    <div id="app">
        <!--vue-ssr-outlet-->
    </div>
    <!-- built files will be auto injected -->
</body>
</html>

以上就是全部要更改和添加的配置項,配置完全部地方就能夠完成服務端渲染。此時須要在package.json中的sctipt中添加啓動項:http:node server.js,就能夠正常運行項目了。注意必定要去訪問服務端設置的端口,同時要保證你的客戶端也是在線的。

總結

這篇博客耗時3天才完成,可能讀起來會很費時間,可是卻有很大的幫助,但願你們可以好好閱讀這篇文章,對你們有所幫助。

感謝你們花費很長時間來閱讀這篇文章,若文章中有錯誤灰常感謝你們提出指正,我會盡快作出修改的。

相關文章
相關標籤/搜索