VUE2--從零實現服務端渲染(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結構