习惯了没有诗的日子,连夏夜的晚风,都当做是你给的惊喜~
各位宝宝,好久不见,最近还好吗?你那里下雨了吗?我这里下雨了,雨声很好听~
speak is cheap ! 开始看正文吧~
一、什么是SSR(服务端渲染)?
vue.js是构建客户端应用程序的框架,在默认情况下,在浏览器输出Vue组件,进行生成DOM和操作DOM。Vue SSR 就是实现将组件渲染为服务器端的HTML字符串,将他们直接发送给浏览器,最后将这些静态标记“激活”为客户端可应用的应用程序。
二、为什么使用SSR
1. 更快的内容到达时间(time-to-content)
之前我们用vue-cli搭建的单页面(SPA)的应用,在我们第一次请求时,服务端返回我们的是一个HTML,外链式的引入了js和css。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="shortcut icon" type=image/x-icon href=static/favicon.ico>
<link href=/static/css/app.XXX.css rel=stylesheet>
<script type=text/javascript src=/static/js/manifest.XXX.js></script>
<script type=text/javascript src=/static/js/vendor.XXX.js></script>
<script type=text/javascript src=/static/js/app.XXX.js></script>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>我是title</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
这样的静态项目要等待js下载完成,执行去渲染DOM。如果页面中的数据需要去服务器请求,则得等到请求返回之后才能渲染出用户想要的页面。然而SSR就不同了,SRR的过程是:
用户访问某页面 -->
服务端接到请求-->
请求数据(此时如果是数据来源于同一个服务器,那就更快了)-->
根据数据渲染出一个HTML字符串直接返回给客户端
所以用户能够更快的看到完整的渲染页面。
2. 更好的SEO
通常情况下SPA应用,要进行异步请求然后展示数据。。但是Google能够很好的进行同步的Javascript应用程序进行索引,它不会等待你的数据回来在进行抓取页面内容。SRR 因为是同步返回整个页面的HTML字符串,是包含程序重要信息的完成页面,so,SSR相比于SPA应用来说能够有利于网站的SEO。
三、核心
Vue SSR核心用代码展示如下:
const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
// 客户端发出任何请求
server.get('*', (req, res) => {
// 创建一个vue实例
const app = new Vue({
data: {
url: req.url
},
template: `<div>访问的 URL 是:{{ url }}</div>`
})
// 传入Vue实例 app 和一个回调函数
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
四、生命周期
只有beforeCreate和created 会在服务端渲染过程中被调用。其他的生命周期钩子函数中的代码,只会在客户端执行。
所以在以上两个生命周期中?写有副作用的代码!比如在纯客户端应用程序中,我们可能会在beforeCreate或者created中设置定时器,然后在beforeDestroy或者destroyed时将其销毁。但是在SSR期间并不会调用销毁钩子函数,所以timer就会一直在。所以为了避免这种情况,可以将副作用代码移到beforMounted 或mounted中。
四、避免创建单例
Node.js服务器是一个长期运行的程序。当我们的代码进入该进程时,他将进行一次取值并留存在内存中。这意味着如果创建一个单例对象,他将在每个传入的请求之间共享。
所以,我们为每个请求创建一个新的根Vue实例。这与每个客户在自己的浏览器中使用新应用程序类似。如果我们在多个请求之间使用一个共享的实例,很容易导致交叉请求状态污染(cross-request state pollution).
所以我们应该暴露一个可以重复执行的工厂函数,为每个请求创建一个新的应用程序实例
// app.js
const Vue = require('vue')
module.exports = function createApp (context) {
return new Vue({
data: {
url: context.url
},
template: `<div>访问的 URL 是:{{ url }}</div>`
})
}
现在我们的服务器代码现在变成:
// server.js
const createApp = require('./app')
server.get('*', (req, res) => {
const context = { url: req.url }
const app = createApp(context)
renderer.renderToString(app, (err, html) => {
// 处理错误……
res.end(html)
})
})
同样的规则也是用于router、store 、event bus 实例。
四、构建步骤
对于客户端应用程序和服务端应用程序我们都需要webpack打包成响应的环境能够是别的程序语言。
Vue SSR打包结果就是生成用于服务端渲染的’服务器‘bundle’,和发送给浏览器的‘客户端bundle’,用户混合静态标记。如图:
五、使用webpack的源码结构
一个基本项目可能像这样:
src
├── components
│ ├── Foo.vue
│ ├── Bar.vue
│ └── Baz.vue
├── App.vue
├── app.js # 通用 entry(universal entry)
├── entry-client.js # 仅运行于浏览器
└── entry-server.js # 仅运行于服务器
app.js : 导出一个工厂函数
import Vue from 'vue'
import App from './App.vue'
// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {
const app = new Vue({
// 根实例简单的渲染应用程序组件。
render: h => h(App)
})
return { app }
}
entry-client.js 创建应用程序
import { createApp } from './app'
// 客户端特定引导逻辑……
const { app } = createApp()
// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')
entry-server.js 导出函数
import { createApp } from './app'
export default context => {
const { app } = createApp()
return app
}
五、客户端激活
客户端激活:Vue在浏览器端接管由服务器端发送的静态HTML,使其变为有Vue管理的动态DOM的过程;
在entry-client.js中,挂载应用程序
// 这里假定 App.vue template 根元素的 `id="app"`
app.$mount('#app')
五、构建配置
我们之前已经了解到我们需要对服务端和客户端两端分别进行打包,所以建议将配置分为三个文件:base ,client, server。base包含两个环境共享的配置。
5.1 服务端配置
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(baseConfig, {
// 将 entry 指向应用程序的 server entry 文件
entry: '/path/to/entry-server.js',
// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
// 并且还会在编译 Vue 组件时,
// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
target: 'node',
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
output: {
libraryTarget: 'commonjs2'
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块。可以使服务器构建速度更快,
// 并生成较小的 bundle 文件。
externals: nodeExternals({
// 不要外置化 webpack 需要处理的依赖模块。
// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
whitelist: /\.css$/
}),
// 这是将服务器的整个输出
// 构建为单个 JSON 文件的插件。
// 默认文件名为 `vue-ssr-server-bundle.json`
plugins: [
new VueSSRServerPlugin()
]
})
emmm,说了这么多,发现自己配置Vue SSR的整个项目真的是够复杂,SO,推荐各位宝宝使用Nuxt.js。。但是知道这些原理会对Nuxt.js帮助巨大的哦!!!
愿我们有能力不向生活缴械投降---Lin