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

posted @ 2021-09-05 19:00  Elwin0204  阅读(1027)  评论(0编辑  收藏  举报