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结构