本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-02-26
服务端渲染(SSR) 指的是把应用程序的 HTML 代码返回给客户端之前先在服务端渲染的过程。
客户端渲染与服务端渲染的区别:
<!-- 客户端渲染 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>document</title>
</head>
<body>
<div id="app"></div>
<script src="/dist/app.js"></script>
</body>
</html>
<!-- 服务端渲染 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>document</title>
</head>
<body>
<div id="app">
<header>
...
</header>
<div class="view">
...
</div>
</div>
<script src="/dist/app.js"></script>
</body>
</html>
两者的区别就在于服务端渲染会把文档初始的 HTML 具体的渲染出来(客户端渲染这一步是由客户端完成的)
在Vue应用中,通过 Vue SSR,服务端可以使用 Vue 生成应用程序的初始 HTML,当代码在客户端运行的时候,只需要激活静态的 Vue HTML,就可以进行交互了,这里的激活指的是,通过向 HTML 添加事件监听器。
而测试 SSR 代码和测试客户端代码的根本区别在于测试代码运行在服务器而不是浏览器。
1、改善搜索引擎优化(SEO)
如上面示例,传统客户端渲染的程序应用是一个 HTML 页,含有一个 div 和 script 元素,当 JavaScript 加载并执行之后,app 才会被渲染进 div 中。然而对于搜索引擎来说,这种提供 web 页面的方式会导致一些问题,搜索引擎使用爬虫机器人来为网站添加索引,爬虫机器人会向网页发出请求,接着为响应(HTML文档)添加索引。然后,搜索引擎会根据已编入索引的响应来判断页面是否包含要向用户显示的相关信息,因此响应包含呈现的页面内容至关重要。
搜索引擎可以 在保存HTML之前 执行页面中的 JavaScript,但并非总是这样,比方说,如果数据是通过异步调用获取的,在异步调用完成数据渲染之前,搜索引擎可能就已经为网站添加好了索引。
当爬虫为网站添加索引时,如果网站还没有被完整渲染,那么网页排名,也就是 SEO 将会受到极大的影响,服务端渲染可以确保搜索引擎正确地为页面内容添加索引,因为从服务器返回的是渲染好的静态 HTML。
2、加快内容生成时间
服务端渲染的另一个优点是,它可以缩短内容到达所需时间,当你请求一个客户端渲染的页面时,在 JavaScript 下载并执行完成并执行之前,页面都会是空白的。而服务端渲染响应会包含应用程序所有的 HTML,因此在浏览器一解析完 HTML 就可以渲染网页内容。
1、SSR会使代码更加复杂
服务端渲染会使代码变得更加复杂,当你编写同时运行在客户端和服务端的代码时,需要考虑代码将在哪个环境中运行。例如,在 node 环境就无法访问 DOM 对象。
2、SSR需要服务器,也会增加服务器负载
服务端渲染需要一个服务器,对于一个正常的 Vue.js 应用程序而言,所有的内容都可以作为静态站点的一部分,使用服务端渲染,你就需要管理服务器并使用它渲染应用程序,这将使得托管和部署应用程序变得负责,而且花费也可能更昂贵。
因为比起静态资源的直接返回,在服务端渲染将会消耗更多的 CPU 资源,也将增加服务器响应时间,你需要确保有效地缓存页面,这样就不会增加太多的内存消耗。
针对服务端渲染的组件,你无法使用 Vue Test Utils 去测试,因为它需要运行在浏览器环境中,它的底层工作方式是它会挂载组件,该组件会创建 DOM 节点。服务端渲染和客户端渲染的代码不同,它会直接返回 HTML 字符串,而无需创建 DOM 节点。
要创建一个由 Vue 实例而来的 HTML 字符串,你需要使用 vue-server-renderer 包,其中暴露的 renderToString 方法会返回一个字符串,该字符串包含从 Vue 实例生成出来的 HTML。
// 安装依赖 npm install vue vue-server-renderer --save
// 注意:这是运行在 node 环境
// 第 1 步:创建一个 Vue 实例
const Vue = require('vue');
const app = new Vue({
template: `<div>Hello World</div>`
})
// 第 2 步:创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer();
// 第 3 步:将 Vue 实例渲染为 HTML
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(html)
// => <div data-server-rendered="true">Hello World</div>
})
// 在 2.5.0+,如果没有传入回调函数,则会返回 Promise:
renderer.renderToString(app).then(html => {
console.log(html)
}).catch(err => {
console.error(err)
})
当为服务端渲染代码编写单元测试时,与之前的所有单元测试无异,就是 **如何在测试中提供输入,然后断言输出。**服务端渲染的输出内容几乎都是一个字符串,因此测试这些组件的方法非常直接,提供一个带有正确输入的组件,然后断言该组件生产的字符串包含了正确的 HTML,还是匹配到了快照。
Vue Server Test Utils 是为服务端渲染组件准备的测试库,它会与 Vue Test Utils 共享相同的挂载 API,接受相同的参数来创建组件实例,不过不会返回包含 Vue 实例的包装器对象,只会返回渲染后的 HTML 字符串。
/**
* @jest-environment node
*/
import { renderToString, render } from '@vue/server-test-utils';
// npm i --save-dev @vue/server-test-utils
import NotFound from '../NotFound.vue'
describe('NotFound', () => {
test('renders correctly on server ', () => {
const str = renderToString(NotFound)
expect(str).toMatchSnapshot()
})
})
需要注意的是, Jest 会默认在 jsdom 环境中运行测试,也就是说,当你运行测试时,DOM 属性如 document 都是定义好的,当你用服务器渲染组件时,就可能导致报错(比如 DOM 对象不存在),单元测试可以通过 (因为 jsdom 环境),而在生产中代码运行却会失败。
当你为服务端渲染组件编写测试时,你应该在 Node 环境中运行测试,而不是在 jsdom 环境中,所以如上示例一样就需要在文件的顶部对这个进行设置。
这同样是需要你把客户端测试和服务端测试抽离到不同的文件的原因,你只能在一个环境中运行其对应的测试文件,可以用不同的扩展名来区分,比如,*.server.spec.js
和 *.spec.js
。
当你使用 Vue Server Test Utils时, VUE_ENV 会被设置为 server,当你把测试分成客户端测试和服务端测试,并使用 Jest 进行测试时,这就很有用,因为 Jest 可以运行每一个测试于新的进程之中。 当你使用其他测试运行程序,而这些测试运行程序没有在单独的进程中运行测试文件,可能会造成很大的问题,这时候你应该使用一个脚本来运行客户端代码的测试,而使用另一个脚本来运行服务端代码的测试。
Vue Server Test Utils 暴露出来两个方法 renderToString 和 render。 renderToString 返回一个已渲染 HTML 标签的字符串,你只需对字符串启用极少的断言即可,如果要遍历服务端渲染的 HTML 标签,那么应该使用 render 方法,它会返回一个类似于普通 Vue Test Utils 包装对象的包装对象。
test('renders 404 inside <h1> tag', () => {
const wrapper = render(NotFound)
expect(wrapper.find('h1').text()).toBe('404')
})
当为应用程序添加 SSR 时,需要用正确的 HTTP 状态码来响应请求,一般来说,使用状态码进行响应时会涉及一些逻辑,因此编写测试以检查服务器是否正确地响应是大有裨益的。
SuperTest 是一个用于测试 HTTP 响应的库,当然,使用 SuperTest 来测试 HTTP 响应会比普通的单元测试花费更多的时间,因为它们需要发送 HTTP 请求。
如果你有许多个使用了 SuperTest 的测试,测试套件可能需要花费几分钟运行,但单元测试而言,几分钟简直太久了,你不应该降低单元测试的速度,取而代之的是,你应该把单元测试和集成测试分离开,确保你的单元测试足够高效。
要实现这一点,可以更改运行 Jest 的参数来完成,如下示例,修改 package.json
的 script
字段:
{
"script": {
"test:unit": "jest src --no-cache"
}
}
这里的 jest src
只在 src 文件夹下运行测试,所以你可以把测试状态码的测试文件放在不同的目录,这里的 –no-cache 标识是用来修复 windows 旧版本的 bug 的。
SuperTest 会使用客户端的 JavaScript 打包文件创建一个运行的应用程序,所以你需要为服务器创建一个新的打包文件以便在每次运行测试时都可以使用。
import request from 'supertest';
// npm i --save-dev supertest
import app from './server';
describe('server', () => {
test('/top returns 200', () => {
return request(app)
.get('/top')
.expect(200)
})
})