[关闭]
@wy 2018-06-21T14:45:37.000000Z 字数 5139 阅读 1091

vue服务端渲染ssr使用过程

未分类


最近看Vue文档和官方给的例子,配置了Vue的服务端渲染,一开始是一脸懵的,根本不知道从何入手,在摸索的过程中不断出问题,然后试图解决,就这样匍匐前进中一步步的调试通了。特此记录我对服务端渲染的认知以及配置过程。如有问题,欢迎一起讨论学习。

服务端渲染

首先先搞明白什么是服务端渲染,服务端渲染其实就是在后端把页面的 HTML 结构拼成字符串的形式,一次性的返回给客户端浏览器。

这里说的渲染可不是像

当然在这个过程中也会有数据的参与,

服务端渲染---直接返回HTML结构

先来看一个在请求之后,服务端返回 Html 结构的例子。

使用 express 模块启动服务,无论访问哪一个路径,都返回一段已经拼接好的html结构:

  1. const express = require('express');
  2. const app = express();
  3. app.get('*',(req,res) => {
  4. res.status(200);
  5. res.setHeader('Content-Type', 'text/html;charset=utf-8;')
  6. // 模拟数据,这段数据可以去数据库查询得到或者请求接口得到
  7. let list = [
  8. {
  9. name: 'Vue'
  10. },
  11. {
  12. name: 'React'
  13. },
  14. {
  15. name: 'Node.js'
  16. }
  17. ]
  18. // 在服务端拼接上结构
  19. var liHtml = list.map((item) => `<li>${item.name}</li>`)
  20. // 向客户端返回整个文档结构
  21. res.end(
  22. `
  23. <!DOCTYPE html>
  24. <html lang="en">
  25. <head>
  26. <meta charset="UTF-8">
  27. <title>服务端返回的html结构</title>
  28. </head>
  29. <body>
  30. <div>
  31. <h2>这段html结构直接从服务端返回,访问路径为:${req.url}</h2>
  32. <ul>
  33. ${liHtml.join('')}
  34. </ul>
  35. </div>
  36. </body>
  37. </html>
  38. `
  39. );
  40. })
  41. app.listen(4000,()=>{
  42. console.log('启动成功')
  43. })

这样在访问时候,返回的是已经生成好的 HTML 结构。如果涉及到数据可以直接通过查询数据库后,生成所需要的 HTML 结构,最终返回到浏览器,浏览器只需要负责解析渲染,而不需要通过javascript动态的生成HTML结构。这样避免了在javascript加载速度慢,或者需要处理的数据庞大而带来的生成HTML结构耗时,导致首屏会出现一闪而过的空白。

以上使用的是ES6的模板字符串来模拟模板引擎渲染,在服务端可以选择 ejs、jade、handler这样的模板引擎。

在浏览器打开源码后,服务端返回的就是一个完整的页面:

客户端渲染---动态生成HTML结构

现在的开发方式大多都要前后端分离,前端负责将数据在UI页面展示,后端负责响应前端所需要的数据。对前端来说,页面的结构需要通过数据动态生成,首先要先向后端发送请求,拿到数据,执行javascript语句生成结构,插入到DOM中,浏览器开始解析渲染。这个过程还是需要消耗一定的时间,所以在没有渲染好之前,会出现首屏空白现象(当然可以加上loading菊花图,让体验变得更好)

写一个前后端分离的例子,体验一下:

后端代码,使用 express模块搭建:

  1. const express = require('express');
  2. const app = express();
  3. // 设置静态文件访问目录
  4. app.use(express.static('public'))
  5. // 响应给前端请求的结构
  6. app.get('/api/list',(req,res) => {
  7. // 模拟数据
  8. let list = [
  9. {
  10. name: 'Vue'
  11. },
  12. {
  13. name: 'React'
  14. },
  15. {
  16. name: 'Node.js'
  17. }
  18. ]
  19. res.send({
  20. success: true,
  21. list,
  22. url: req.url
  23. })
  24. })
  25. app.get('*',(req,res) => {
  26. res.status(200);
  27. res.setHeader('Content-Type', 'text/html;charset=utf-8;')
  28. // 只发送给前端一个页面
  29. res.sendFile(__dirname+ '/index.html');
  30. })
  31. app.listen(4000,()=>{
  32. console.log('启动成功')
  33. })

向前端发送的HTML页面,代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>vue-ssr</title>
  6. </head>
  7. <body>
  8. <div id="app"></div>
  9. <script src="/test.js"></script>
  10. </body>
  11. </html>

从以上代码中可以看出来,当请求时,后端只向前端发送一个 index.html 页面,这个页面中只有一个空内容的 div 元素,页面中不会显示任何内容,这就是首屏出现空白的原因,因为没有任何元素显示。那么在这之后,页面中怎么有内容展示了呢?这要说到 test.js 中代码的作用:

  1. // 向后端发请求获取数据
  2. fetch('/api/list')
  3. .then((data) => {
  4. return data.json(); // json字符串转为可操作的对象
  5. })
  6. .then(({list,url}) => {
  7. // 拿到数据后渲染在页面中
  8. var app = document.getElementById('app');
  9. var newUl = document.createElement('ul')
  10. var liHtml = list.map((item) => `<li>${item.name}</li>`);
  11. newUl.innerHTML = liHtml.join('');
  12. app.innerHTML = `<h2>这段html结构在客户端通过javascript生成,访问路径为:${url}</h2>`
  13. app.appendChild(newUl)
  14. })

在文件 test.js 中,通过 fetch 向后端发送请求,拿到数据,然后生成拼接一些列的 HTML 结构,插入到页面在已经存在的元素挂载点id为 app 的div上,这样就显示出了内容。

现在模拟的数据量不是很庞大,请求也很快,所以在测试时候,那一闪而过的首页空白时间短的可以忽略不计,我们可以使用定时器来延迟一下模拟延长渲染的时间:

  1. // 省略其他代码
  2. ...
  3. setTimeout(() => {
  4. app.innerHTML = `<h2>这段html结构在客户端通过javascript生成,访问路径为:${url}</h2>`
  5. app.appendChild(newUl)
  6. },1000)
  7. ...
  8. // 省略其他代码

在一秒之内页面是空白一片,一秒之后出现了内容。

在浏览器打开源码后查看,服务端返回的页面只有一个空的div标签:

vue实现服务端渲染

按照正常使用 Vue 的写项目的流程,使用 new Vue 启动整个应用,代码如下:

  1. const vueApp = new Vue({
  2. data: {
  3. message: 'hello,vue-ssr'
  4. },
  5. template: `
  6. <div>
  7. <h1>欢迎学习vue-ssr</h1>
  8. <p>{{message}}</p>
  9. </div>
  10. `
  11. })

注意上面的代码在选项对象传入时,是没有传入挂载点选项 el 的,因为在服务端是不需要挂载点进行展示的,而是要把这段启动应用的程序转成HTML结构字符串。这就需要安装一个专门做服务端渲染的模块,由vue官方提供,安装模块:

npm i vue-server-renderer -S

安装模块后引入使用,会暴露一个函数 createRenderer , 通过这个方法创建 Renderer 实例,使用如下:

const { createRenderer } = require('vue-server-renderer')
const renderer = createRenderer({ /* 选项 */ })

现在就有了一个 renderer 实例可以使用,调用其下面的方法 renderToStringnew Vue 根实例传入,返回值为一个 Promise 对象,拿到HTML字符串

  1. vueServerRender.renderToString(vueApp).then((html) => {
  2. console.log(html)
  3. }).catch(err => console.log(err))

得到的HTML字符串为:

  1. <div data-server-rendered="true"><h1>欢迎学习vue-ssr</h1> <p>hello,vue-ssr</p></
  2. div>

其中 data-server-rendered="true" 是一个标示,代表的是在服务端渲染出来的结构,这个后面要和客户端的代码混合会使用到。

以上的方式类似于模板引擎提供的函数一样,最终把数据和模板结合在一起,返回结合后的HTML字符串,最终可以将此字符串直接返回到客户端浏览器。

使用 express 创建服务的完整代码如下,创建 index.js 文件:

  1. const express = require('express');
  2. const app = express();
  3. const Vue = require('vue');
  4. const {createRenderer} = require('vue-server-renderer')
  5. const vueServerRender = createRenderer()
  6. // 无论访问那个路由都走进来
  7. app.get('*',(req,res) => {
  8. res.status(200);
  9. res.setHeader('Content-Type', 'text/html;charset=utf-8;')
  10. // 实例
  11. const vueApp = new Vue({
  12. data:{
  13. message: 'hello,vue-ssr'
  14. },
  15. template: `
  16. <div>
  17. <h1>欢迎学习vue-ssr</h1>
  18. <p>{{message}}</p>
  19. </div>
  20. `
  21. })
  22. vueServerRender.renderToString(vueApp).then((html) => {
  23. // 向客户端返回页面HTML结构
  24. res.end(
  25. `
  26. <!DOCTYPE html>
  27. <html lang="en">
  28. <head>
  29. <meta charset="UTF-8">
  30. <title>vue-ssr</title>
  31. </head>
  32. <body>
  33. ${html}
  34. </body>
  35. </html>
  36. `
  37. );
  38. }).catch(err => console.log(err))
  39. })
  40. app.listen(4000,()=>{
  41. console.log('启动成功')
  42. })

注意上面的代码,在每一次访问都会创建一个新的实例,这防止公用一个实例数据间的交叉污染,比如第一个用户访问时候产生了一个数据为 hello , 如果是用的同一个实例的话,第二个用户也会访问到 hello 这个数据,所以保证每一次都返回的是全新的实例。但这样访问量过大时候,会非常耗内存,好在是有缓存可以使用,可以把一些页面缓存一下,规定一个过期时间,在规定的时间内访问的都是同一个结合后的 HTML结构。

也可以使用HTML模板,看起来更加简单些,在根目录下创建 index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>vue-ssr</title>
  6. </head>
  7. <body>
  8. <!--vue-ssr-outlet-->
  9. </body>
  10. </html>

模板中的注释部分 < !--vue-ssr-outlet--> 必须要添加上,将来实例转换后的HTML结构会替换在这个位置,在服务端代码需要稍作如下改动:

  1. const path = require('path');
  2. const {createRenderer} = require('vue-server-renderer')
  3. const vueServerRender = createRenderer({
  4. //配置选项,读取模板的内容
  5. template: require('fs').readFileSync(path.join(__dirname,"./index.html"),'utf-8')
  6. })
  7. ... // 代码省略
  8. // 发送到客户端浏览器
  9. vueServerRender.renderToString(vueApp).then((html) => {
  10. res.end(html);
  11. }).catch(err => console.log(err))

此时就完成了初步的vue服务端渲染的体验。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注