1. 程式人生 > 程式設計 >vuecli專案構建SSR服務端渲染的實現

vuecli專案構建SSR服務端渲染的實現

服務端渲染(SSR)

將一個 Vue 元件在服務端渲染成 HTML 字串併發送到瀏覽器,最後將這些靜態標記“啟用”為可互動應用程式的過程就叫服務端渲染(SSR)

伺服器渲染的 Vue.js 應用程式也可以被認為是"同構"或"通用",因為應用程式的大部分程式碼都可以在伺服器和客戶端上執行

為什麼使用 服務端渲染(SSR)

  • 更好的 SEO:傳統的 spa 頁面資料都是非同步載入,搜尋引擎爬蟲無法抓取,服務端渲染(SSR)使搜尋引擎爬蟲抓取工具可以直接檢視完全渲染的頁面,解決 vue 專案的 seo 問題
  • 更快的內容到達時間 (首屏載入更快):請求頁面時,服務端將渲染好的頁面直接傳送給瀏覽器進行渲染,瀏覽器只需要解析渲染 HTML,無需等待所有的 JavaScript 都完成下載並執行,才顯示伺服器渲染的標記

服務端渲染(SSR)缺點

  • 開發條件所限:瀏覽器特定的程式碼,只能在某些生命週期鉤子函式中使用;一些外部擴充套件庫可能需要特殊處理,才能在伺服器渲染應用程式中執行
  • 涉及構建設定和部署的更多要求:與可以部署在任何靜態檔案伺服器上的完全靜態單頁面應用程式 (SPA) 不同,伺服器渲染應用程式,需要處於 Node.js server 執行環境
  • 更多的伺服器端負載:在 Node.js 中渲染完整的應用程式,顯然會比僅僅提供靜態檔案的 server 更加大量佔用 CPU 資源,因此如果你預料在高流量環境下使用,需要準備相應的伺服器負載,並採用快取策略

服務端渲染(SSR)vs 預渲染(Prerendering)

如果你只是想改善少數營銷頁面(例如 /,/about,/contact 等)的 SEO,那麼你可能需要預渲染,無需使用 web 伺服器實時動態編譯 HTML,而是使用預渲染方式,在構建時簡單地生成針對特定路由的靜態 HTML 檔案,優點是設定預渲染更簡單,並可以將你的前端作為一個完全靜態的站點
如果你使用 webpack,你可以使用 prerender-spa-plugin (npm地址) 外掛輕鬆地新增預渲染

服務端渲染(SSR)原理

構建流程:所有的檔案擁有一個公共入口 app.js,進入服務端入口 entry-server.js 和客戶端入口 entry-client.js ,專案完成後通過使用 webpack 打包生成服務端 server bundle

(一個供服務端 SSR 使用的 json 檔案)和客戶端 client bundle(用於瀏覽器),當請求頁面時,服務端將 vue 元件組裝成 HTML 字串傳送到瀏覽器,混入到客戶端訪問的 HTML 模板中,完成頁面渲染

在這裡插入圖片描述

通過 vuecli 建立 vue 專案

vue create vue-ssr-demo

vue-server-renderer

vue-server-renderer 是 SSR 渲染的核心,提供 createRenderer 方法,這個方法的 renderToString 可以把 app 渲染成字串。createBundleRenderer 方法可以通過預打包應用程式程式碼建立 bundleRenderer 例項,來渲染 bundle 和 HTML 模板

安裝 vue-server-renderer

npm install vue-server-renderer --save

注意:

  • vue-server-renderer 和 vue 必須匹配版本
  • vue-server-renderer 依賴一些 Node.js 原生模組,因此只能在 Node.js 中使用

避免狀態單例

Node.js 伺服器是一個長期執行的程序,當我們的程式碼進入該程序時,它將進行一次取值並留存在記憶體中,這意味著如果建立一個單例物件,它將在每個傳入的請求之間共享,所以我們應該暴露一個可以重複執行的工廠函式,為每個請求建立一個新的根 Vue 例項,如果我們在多個請求之間使用一個共享的例項,很容易導致交叉請求狀態汙染(同樣的規則也適用於 router、store 和 event bus 例項)

建立 路由 router

安裝 vue-router

npm install vue-router --save

src 目錄下建立 router 資料夾和 index.js
在 components 目錄下建立 Home.vue 和 About.vue 頁面(根據專案需求自定義建立)

router/index.js:

import Vue from "vue"
import Router from "vue-router"

import Home from "@/components/Home"
import About from "@/components/About"

Vue.use(Router)

//每次使用者請求都需要建立一個新的router例項
//建立createRouter工廠函式
export default function createRouter() {
  //建立router例項
  return new Router({
    mode: "history",routes: [
      {
        path: "/",name: 'home',component: Home
      },{
        path: "/about",name: 'about',component: About
      }
    ]
  })
}

修改 App.vue

修改 App.vue 頁面,進行頁面佈局(根據專案需求自定義佈局)

App.vue:

<template>
 <div id="app">
  <nav>
   <router-link to="/">首頁</router-link>
   <router-link to="/about">關於</router-link>
  </nav>
  <router-view></router-view>
 </div>
</template>

建立 公共入口 app.js

src 目錄下建立 公共入口 app.js ,用於建立 vue 例項

app.js:

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

//建立createApp工廠函式
export default function createApp() {
  const router = createRouter()
  //建立vue例項
  const app = new Vue({
    router,render: h => h(App),})
  return { app,router }
}

建立 服務端入口 entry-server.js

src 目錄下建立 服務端入口 entry-server.js ,用於渲染首屏

entry-server.js:

import createApp from "./app"

export default context => {
  return new Promise((resolve,reject) => {
    const { app,router } = createApp()
    //渲染首屏
    router.push(context.url)
    router.onReady(() => {
      resolve(app)
    },reject)
  })
}

建立 客戶端入口 entry-client.js

src 目錄下建立 客戶端入口 entry-client.js ,用於掛載啟用 app

entry-client.js:

import createApp from "./app"

const { app,router } = createApp()
router.onReady(() => {
  //掛載啟用app
  app.$mount("#app")
})

建立 頁面模板 index.temp.html

public 目錄下建立 index.temp.html ,作為渲染 Vue 應用程式時,renderer 生成 HTML 頁面包裹容器,來包裹生成的 HTML 標記
<!--vue-ssr-outlet--> 註釋將是應用程式 HTML 標記注入的地方

index.temp.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>vue ssr</title>
</head>
<body>
  <!--vue-ssr-outlet-->
</body>
</html>

建立 Node.js 伺服器

服務端渲染(SSR)需要使用 Node.js 伺服器,這裡使用 express 框架搭建
安裝 express

npm install express --save

根目錄下建立 server.js 檔案,用於搭建 Node.js 伺服器

server.js:

//nodejs伺服器
const express = require("express")
const Vue = require("vue")
const fs = require("fs")

//建立express例項
const app = express()
//建立渲染器
const { createBundleRenderer } = require("vue-server-renderer")
const serverBundle = require("./dist/server/vue-ssr-server-bundle.json")
const clientManifest = require("./dist/client/vue-ssr-client-manifest.json")
const renderer = createBundleRenderer(serverBundle,{
  runInNewContext: false,template: fs.readFileSync("./public/index.temp.html","utf-8"),//頁面模板
  clientManifest
})

//中介軟體處理靜態檔案請求
app.use(express.static("./dist/client",{index: false}))

//將路由的處理交給vue
app.get("*",async (req,res) => {
  try {
    const context = {
      url: req.url,title: ""
    }
    const html = await renderer.renderToString(context)
    res.send(html)
  }catch {
    res.status(500).send("伺服器內部錯誤!")
  }
})

app.listen(9999,() => {
  console.log("伺服器渲染成功!")
})

webpack 打包配置

根目錄下建立 vue 配置檔案 vue.config.js 進行 webpack 配置,該配置會覆蓋 vue-cli 中 webpack 的預設配置

vue.config.js:

const VueSSRServerPlugin = require("vue-server-renderer/server-plugin")
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin")

//環境變數,決定入口是客戶端還是服務端
const TARGRT_NODE = process.env.WEBPACK_TARGET === "node"
const target = TARGRT_NODE ? "server" : "client"

module.exports = {
  css: {
    extract: false
  },outputDir: "./dist/" + target,configureWebpack: () => ({
    //將 entry 指向應用程式的 server entry 檔案
    entry: `./src/entry-${target}.js`,//對 bundle renderer 提供 source map 支援
    devtool: "source-map",//這允許 webpack 以 Node 適用方式(Node-appropriate fashion)處理動態匯入(dynamic import)
    //並且還會在編譯 Vue 元件時,告知 `vue-loader` 輸送面向伺服器程式碼(server-oriented code)
    target: TARGRT_NODE ? "node" : "web",node: TARGRT_NODE ? undefined : false,output: {
      //此處告知 server bundle 使用 Node 風格匯出模組(Node-style exports)
      libraryTarget: TARGRT_NODE ? "commonjs2" : undefined
    },optimization: { splitChunks: TARGRT_NODE ? false : undefined },//將伺服器的整個輸出構建為單個 JOSN 檔案的外掛
    //服務端預設檔名為 vue-ssr-server-bundle.json
    plugins: [TARGRT_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
  })
}

打包指令碼配置

cross-env 外掛:執行跨平臺設定和使用環境變數的指令碼
安裝 cross-env 外掛

npm install cross-env --save

package.json 檔案中定義專案執行打包指令碼

package.json:

{
	......
	"scripts": {
		"server": "node server","build:client": "vue-cli-service build","build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server","build": "npm run build:server && npm run build:client"
	},......
}

終端執行命令打包專案:

npm run build

打包完成後,終端執行命令啟動 Node.js 伺服器

npm run server

伺服器啟動後,瀏覽器開啟 localhost:9999 即可訪問 SSR 專案
檢視網頁原始碼發現根元素上添加了一個特殊的屬性:data-server-rendered,該屬性讓客戶端 Vue 知道這部分 HTML 是由 Vue 在服務端渲染的,並且應該以啟用模式進行掛載

<div id="app" data-server-rendered="true">......</div>

在這裡插入圖片描述

專案目錄:

在這裡插入圖片描述

打包後 dist 目錄:

在這裡插入圖片描述

到此這篇關於vuecli專案構建SSR服務端渲染的實現的文章就介紹到這了,更多相關vuecli構建SSR服務端渲染內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!