溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時間:2020-09-21 08:11:09 來源:腳本之家 閱讀:429 作者:Aaron 欄目:web開發(fā)

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

什么是SSR

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

為什么要使用SSR

  • 更好的SEO,搜索引擎爬蟲爬取工具可以直接查看完全渲染的頁面
  • 更寬的內(nèi)容達到時間(time-to-content),當權(quán)請求頁面的時候,服務(wù)端渲染完數(shù)據(jù)之后,把渲染好的頁面直接發(fā)送給瀏覽器,并進行渲染。瀏覽器只需要解析html不需要去解析js。

SSR弊端

  1. 開發(fā)條件受限,Vue組件的某些生命周期鉤子函數(shù)不能使用
  2. 開發(fā)環(huán)境基于Node.js
  3. 會造成服務(wù)端更多的負載。在Node.js中渲染完整的應(yīng)用程序,顯然會比僅僅提供靜態(tài)文件server更加占用CPU資源,因此如果你在預(yù)料在高流量下使用,請準備響應(yīng)的服務(wù)負載,并明智的采用緩存策略。

準備工作

在正式開始之前,在vue官網(wǎng)找到了一張這個圖片,圖中詳細的講述了vue中對ssr的實現(xiàn)思路。如下圖簡單的說一下。

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

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

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

mkdir dome
cd dome
npm init

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

{
 "name": "dome",  // 項目名稱
 "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"   // 許可證
}

初始化完成之后接下來需要安裝,項目所需要依賴的包,所有依賴項如下:

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

如上所有依賴項一一安裝即可,安裝完成之后就可以進行下一步了。前面說過SSR是服務(wù)端預(yù)渲染,所以當然要創(chuàng)建一個Node服務(wù)來支撐。在dome文件夾下面創(chuàng)建一個index.js文件,并使用express創(chuàng)建一個服務(wù)。

代碼如下:

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

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

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

完成上述代碼之后,為了方便我們需要在package.json添加一個命令,方便后續(xù)開發(fā)啟動項目。

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

創(chuàng)建好之后,在命令行直接輸入npm start即可,當控制臺顯示服務(wù)已啟動則表示該服務(wù)已經(jīng)啟動成功了。接下來需要打開瀏覽器看一下渲染的結(jié)果。在瀏覽器地址欄輸入locahost:3000則可以看到ok兩個字。

SSR渲染手動搭建

前面的準備工作已經(jīng)做好了,千萬不要完了我們的主要目的不是為了渲染文字,主要的目標是為了渲染*.vue文件或html所以。接下來就是做我們想要做的事情了。接下來就是要修改index.js文件,將之前安裝的`vue
和vue-server-render`引入進來。

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

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

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

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

改動如下:

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:`<h2>{{message}}</h2>` 
  });
  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ù)已啟動")
});

上述操作完成之后,一定要記得保存,然后重啟服務(wù)器,繼續(xù)訪問一下locahost:3000,就會看到在服務(wù)端寫入的HTML結(jié)構(gòu)了。這樣做好像給我們添加了大量的工作,到底與在web端直接使用有什么區(qū)別么?

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

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

模板現(xiàn)在有了該如何使用?在creteRender函數(shù)可以接收一個對象作為配置參數(shù)。配置參數(shù)中有一項為template,這項配置的就是我們即將使用的Html模板。這個接收的不是一個單純的路徑,我們需要使用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端進行開發(fā)的時候,需要掛在一個el的掛載點,這樣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里面添加了一段注釋,當將vueServerRender編譯好的html傳到模板當中之后這個地方將被替換成服務(wù)端預(yù)編譯的模板內(nèi)容,這樣也算是完成一個簡單的服務(wù)端預(yù)渲染了。雖然寫入的只是簡單的html渲染,沒有數(shù)據(jù)交互也沒有頁面交互,也算是一個不小的進展了。

使用SSR搭建項目我們繼續(xù)延續(xù)上個項目繼續(xù)向下開發(fā),大家平時在使用vue-cli搭建項目的時候,都是在src文件夾下面進行開發(fā)的,為了和vue項目結(jié)構(gòu)保持一致,同樣需要創(chuàng)建一個src文件夾,并在src文件夾創(chuàng)建conponents,router,utils,view,暫定項目結(jié)構(gòu)就這樣,隨著代碼的編寫會逐漸向項目里面添加內(nèi)容。

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

初始的目錄結(jié)構(gòu)已經(jīng)搭建好了之后,接下來需要繼續(xù)向下進行,首先要做的就是要在router目錄中添加一個index.js文件,用來創(chuàng)建路由信息(在使用路由的時候一定要確保路由已經(jīng)安裝)。路由在項目中所起到的作用應(yīng)該是重要的,路由會通過路徑把頁面和組件之間建立聯(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:`<h2>這里是首頁<h2/>`
        },
        name:"home"
      },
      {
        path:"/about",
        component:{
          template:`<h2>這里是關(guān)于頁<h2/>`
        },
        name:"about"
      }
    ]
  })
}

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

現(xiàn)在Vue實例和服務(wù)端混在一起,這樣對于項目的維護是很不好的,所以也需要把Vue從服務(wù)端單獨抽離出來,放到app.js中去。這里采用和router同樣的方式使用工廠方式,以保證每次被訪問都是一個全新的vue實例。在app.js導入剛剛寫好的路由,在每次觸發(fā)工廠的時候,創(chuàng)建一個新的路由實例,并綁定到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>
        <h2>{{message}}</h2>
        <ul>
          <li>,
            <router-link to="/">首頁<router-link/>
          </li>
          <li>
            <router-link to="/about">關(guān)于我<router-link/>
          </li>
        </ul>
      </div>
      <router-view></router-view>
    ` 
  });
}

做完這些東西貌似好像就能用了一樣,但是還是不行,仔細想想好像忘了一些什么操作,剛剛把vue實例從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ù)
  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ù)已啟動")
});

準備工作都已經(jīng)做好啦,完事具備只欠東風啦?,F(xiàn)在運行一下npm start可以去頁面上看一下效果啦??吹巾撁嬷幸呀?jīng)渲染出來了,但是好像是少了什么?雖然導航內(nèi)容已經(jīng)都顯示出來了,但是路由對應(yīng)的組件好像沒得渲染噻。具體是什么原因?qū)е碌哪?,vue-router是由前端控制渲染的,當訪問路由的時候其實,在做首屏渲染的時候并沒有授權(quán)給服務(wù)端讓其去做渲染路由的工作。(⊙﹏⊙),是的我就是這么懶...

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

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

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

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

由于我們不能預(yù)測到用戶所訪問的路由就是在vue-router中所配置的,所以需要在onReady的時候進行處理,我們可以通過router的getMatchedComponents這個方法,獲取到我們所導入的組件,這些有個我們就可通過判斷組件對匹配結(jié)果進行渲染。

entry-server.js

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

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

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

index.js:

const express = require("express");
const App = require("./src/entry-server.js");
const vueServerRender = require("vue-server-render").creteRender();

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

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

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

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

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

entry-client.js

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

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

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

vue-cli項目搭建

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

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

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

文件分別是:

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

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

npm install vue-server-render -S

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

webpack.dev.conf.js

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

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

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

服務(wù)端配置

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

在開始服務(wù)端配置之前,需要在src文件夾下面創(chuàng)建三個文件,app.js,entry-client.js,entry-server.js,創(chuàng)建完成之后需要對其寫入相關(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",
    routers:[
      {
        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,
    component:{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.onRady(() => {
      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(手動創(chuàng)建)

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


const vueSSRServerPlugin = require("vue-server-render/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 webapck.DefinePlugin({
      'process.env.NODE_ENV':'"devlopment"',
      'process.ent.VUE_ENV': '"server"'
    }),
    new vueSSRServerPlugin()
  ]
});

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

const serverConf = require("./webpack.server.conf");
const webpack = require("webpack");
const fs = require("fs");
const path = require("path");
// 讀取內(nèi)存中的.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(手動創(chuàng)建)

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

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

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

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>

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

總結(jié)

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

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI