1. 程式人生 > 其它 >VUE2--從零實現服務端渲染(SSR)

VUE2--從零實現服務端渲染(SSR)

Vue.js 是構建客戶端應用程式的框架。預設情況下,可以在瀏覽器中輸出 Vue 元件,進行生成 DOM 和操作 DOM。然而,也可以將同一個元件渲染為伺服器端的 HTML 字串,將它們直接傳送到瀏覽器,最後將這些靜態標記"啟用"為客戶端上完全可互動的應用程式。在服務端渲染模板字串的這一過程即為SSR。

概要

Vue.js 是構建客戶端應用程式的框架。預設情況下,可以在瀏覽器中輸出 Vue 元件,進行生成 DOM 和操作 DOM。然而,也可以將同一個元件渲染為伺服器端的 HTML 字串,將它們直接傳送到瀏覽器,最後將這些靜態標記"啟用"為客戶端上完全可互動的應用程式。在服務端渲染模板字串的這一過程即為SSR。

何時使用伺服器端渲染 (SSR) (來自vue官方文件)

與傳統 SPA (單頁應用程式 (Single-Page Application)) 相比,伺服器端渲染 (SSR) 的優勢主要在於:

  • 更好的 SEO,由於搜尋引擎爬蟲抓取工具可以直接檢視完全渲染的頁面。

請注意,截至目前,Google 和 Bing 可以很好對同步 JavaScript 應用程式進行索引。在這裡,同步是關鍵。如果你的應用程式初始展示 loading 菊花圖,然後通過 Ajax 獲取內容,抓取工具並不會等待非同步完成後再行抓取頁面內容。也就是說,如果 SEO 對你的站點至關重要,而你的頁面又是非同步獲取內容,則你可能需要伺服器端渲染(SSR)解決此問題。

  • 更快的內容到達時間 (time-to-content),特別是對於緩慢的網路情況或執行緩慢的裝置。無需等待所有的 JavaScript 都完成下載並執行,才顯示伺服器渲染的標記,所以你的使用者將會更快速地看到完整渲染的頁面。通常可以產生更好的使用者體驗,並且對於那些「內容到達時間(time-to-content) 與轉化率直接相關」的應用程式而言,伺服器端渲染 (SSR) 至關重要。

使用伺服器端渲染 (SSR) 時還需要有一些權衡之處:

  • 開發條件所限。瀏覽器特定的程式碼,只能在某些生命週期鉤子函式 (lifecycle hook) 中使用;一些外部擴充套件庫 (external library) 可能需要特殊處理,才能在伺服器渲染應用程式中執行。

涉及構建設定和部署的更多要求。與可以部署在任何靜態檔案伺服器上的完全靜態單頁面應用程式 (SPA) 不同,伺服器渲染應用程式,需要處於 Node.js server 執行環境。

  • 更多的伺服器端負載。在 Node.js 中渲染完整的應用程式,顯然會比僅僅提供靜態檔案的 server 更加大量佔用 CPU 資源 (CPU-intensive - CPU 密集),因此如果你預料在高流量環境 (high traffic) 下使用,請準備相應的伺服器負載,並明智地採用快取策略。

在對你的應用程式使用伺服器端渲染 (SSR) 之前,你應該問的第一個問題是,是否真的需要它。這主要取決於內容到達時間 (time-to-content) 對應用程式的重要程度。例如,如果你正在構建一個內部儀表盤,初始載入時的額外幾百毫秒並不重要,這種情況下去使用伺服器端渲染 (SSR) 將是一個小題大作之舉。然而,內容到達時間 (time-to-content) 要求是絕對關鍵的指標,在這種情況下,伺服器端渲染 (SSR) 可以幫助你實現最佳的初始載入效能。

SSR的原理

通過如下的原理圖我們可以分析出SSR的基本原理:

左側Source部分就是我們編寫的原始碼,所有程式碼有一個公共入口,即app.js,與app.js右側相鄰的就是服務端的入口
(entry-server.js)和客戶端的入口(entry-client.js)。當完成所有原始碼的編寫之後,我們通過webpack打包出兩個bundle,分別是server bundle和client bundle,當用戶進行頁面訪問的時候,先是經過服務端的入口,將vue元件組裝為html字串,並混入客戶端所訪問的html模板中,最終就完成了整個SSR渲染的過程。

建立SSR demo

初始化專案並安裝依賴

建立一個專案目錄為vue-ssr-demo, 進入目錄,輸入如下命令:

// 初始化專案
npm init
// 安裝依賴
// 用於建立node服務
npm install express -D
npm install vue -S
npm install vue-router -S
// 用於將 Vue 例項渲染為 HTML
npm install vue-server-renderer -S

安裝完成後,可以看到我們的package.json檔案如下:

{
  "name": "vue-ssr-demo",
  "version": "1.0.0",
  "description": "vue ssr demo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "ssr"
  ],
  "author": "elwin",
  "license": "ISC",
  "dependencies": {
    "vue": "^2.6.14",
    "vue-router": "^3.5.2",
    "vue-server-renderer": "^2.6.14"
  },
  "devDependencies": {
    "express": "^4.17.1"
  }
}

建立node服務

在根目錄下(vue-ssr-demo)新建一個server.js檔案,使用者建立node服務

const express = require('express')
const app = express()

// express中用於匹配並處理一個特定的請求(*代表匹配所有請求),且請求方法必須是GET
app.get('*', (req, res) => {
  res.end('Hell SSR')
})

// 監聽5000埠
app.listen(5000, () => {
  console.log('服務已開啟^_^')
})

在package.json檔案中新增啟動node服務的命令

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

在vscode終端輸入 npm run server 可以看到服務已啟動的提示語,在瀏覽器位址列輸入localhost:5000,可以看到頁面顯示Hell SSR

渲染一個HTML頁面

上面我們成功渲染出一段文字,下面我們去渲染一個html模板。
修改server.js檔案如下:

const express = require('express')
const app = express()
// 引入vue
const Vue = require('vue')
// 建立一個 renderer
const VueServerRender = require('vue-server-renderer').createRenderer()

// express中用於匹配並處理一個特定的請求(*代表匹配所有請求),且請求方法必須是GET
app.get('*', (req, res) => {
  // 初始化vue例項
  const VueApp = new Vue({
    data: {
      msg: 'Hello SSR'
    },
    template: `<h1>這是模板訊息:{{ msg }}</h1>`
  })

  res.status(200)
  // 設定響應頭型別,字元編碼
  res.header('Content-type', 'text/html;charset=utf-8')
  // 將 Vue 例項渲染為 HTML,在 2.5.0+,如果沒有傳入回撥函式,則會返回 Promise
  VueServerRender.renderToString(VueApp).then(html => {
    res.end(html)
  }).catch(err => {
    console.log(err)
  })
})

// 監聽5000埠
app.listen(5000, () => {
  console.log('服務已開啟^_^')
})

儲存程式碼,重啟服務npm run server,然後重新重新整理頁面, 可以看到渲染的文字為這是模板訊息:Hello SSR
瀏覽器按F12檢視頁面原始碼,我們發現在原始碼中,已經存在一個標籤h1,同時,h1標籤上面有一個屬性:data-server-rendered="true",這其實是一個標記,表明這個頁面是由vue-ssr渲染而來的。
雖然h1標籤對被成功渲染,但是我們發現這個html頁面並不完整, 缺少文件宣告,html標籤,body標籤,title標籤等。

使用一個頁面模板

當你在渲染 Vue 應用程式時,renderer 只從應用程式生成 HTML 標記 (markup)。我們必須用一個額外的 HTML 頁面包裹容器,來包裹生成的 HTML 標記。
為了簡化這些,你可以直接在建立 renderer 時提供一個頁面模板。多數時候,我們會將頁面模板放在特有的檔案中,例如 index.template.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue-SSR-demo</title>
</head>
<body>
  <!--vue-ssr-outlet-->
</body>
</html>

注意 <!--vue-ssr-outlet--> 註釋 -- 這裡將是應用程式 HTML 標記注入的地方。

然後,我們可以讀取和傳輸檔案到 Vue renderer 中,修改server.js

const express = require('express')
const app = express()
// 引入vue
const Vue = require('vue')
// 建立一個 renderer
const VueServerRender = require('vue-server-renderer').createRenderer({
  // createRenderer函式可以接收一個物件作為配置引數。配置引數中有一項為template,就是我們使用的Html模板。我們需要使用fs模組將html模板讀取出來。
  template: require('fs').readFileSync('./index.template.html', 'utf-8')
})

// express中用於匹配並處理一個特定的請求(*代表匹配所有請求),且請求方法必須是GET
app.get('*', (req, res) => {
  // 初始化vue例項
  const VueApp = new Vue({
    data: {
      msg: 'Hello SSR'
    },
    template: `<h1>這是模板訊息:{{ msg }}</h1>`
  })

  res.status(200)
  // 設定響應頭型別,字元編碼
  res.header('Content-type', 'text/html;charset=utf-8')
  // 將 Vue 例項渲染為 HTML,在 2.5.0+,如果沒有傳入回撥函式,則會返回 Promise
  VueServerRender.renderToString(VueApp).then(html => {
    res.end(html)
  }).catch(err => {
    console.log(err)
  })
})

// 監聽5000埠
app.listen(5000, () => {
  console.log('服務已開啟^_^')
})

重新啟動node服務,重新整理頁面,按下F12,這時我們可以看到完整的html結構