乐趣区

Vuejs-SSR-内容总结

本文只是对 Vue.js 官方 SSR 文档和对官方 hackernews demo 的个人学习总结,说得不够完整的请见谅

本文主要对以下几方面内容对 Vue.js SSR 的内容进行分析总结

  • SSR 出现的原因
  • Vue.js SSR 的总体原理
  • SSR 当中的数据预取
  • SSR 在编写代码时候的限制
  • SSR 的 webpack 构建原理

SSR 出现的原因

  1. 单页应用有一个很大的缺点就是 SEO 问题,搜索引擎目前只能对同步的 javascript 进行索引,但对于需要异步获取数据的单页应用来说,搜索引擎并不会抓取到它们的内容
  2. 更快的首屏内容展示速度,单页应用需要等待 JS 文件加载完成,然后再进行页面渲染,而 SSR 是将渲染完毕的 html 传输给客户端

Vue.js SSR 的总体原理

如果用一句话概括 Vue.js SSR 的运作过程,那就是 在服务端将 Vue.js 实例转换成 html 字符串传输到客户端,然后进行客户端激活,使网页内容能在 Vue 实例的控制之下

这一句话包含两步内容

  1. Vue.js 实例转换成 html 字符串
  2. 客户端激活

先来看第一步

Vue.js 应用转换成 html 字符串

一个最简单的 Vue.js 单页应用是这样的:

new Vue({render: h => h('div', '123')
}).$mount('#app')

这里也包含两步

  1. 新建 Vue 实例
  2. 挂在到 DOM 上面

在服务端当中我们不进行上面第二步操作,取而代之的是将这个实例直接渲染成字符串,做这个工作的就是我们的vue-server-renderer

const renderer = require('vue-server-renderer').createRenderer()
const Vue = require('vue')

renderer.renderToString(new Vue({render: h => h('div', 123)
})).then(html => {console.log(html)
}).catch(err => {console.error(err)
})
// 输出 <div data-server-rendered="true">123</div>

到现在一个最简单的 vue ssr 应用在服务端的工作已经完成了,下面我们转向下一步 客户端激活

客户端激活

客户端激活跟我们单页应用所做的工作相比,最大的不同点就是它并不会构建 DOM 元素,只会对现有的 DOM 元素进行 激活 ,使它们能被 Vue 实例进行控制,而判断激活的关键就是上面的data-server-rendered 属性

至此,最简单的一个 SSR 应用已经构建完成了,下面是对这个应用的功能进行进一步的补充

SSR 当中的数据预取

数据预取包含着两个方面,客户端的数据预取和服务端的数据预取

服务端的数据预取

我们渲染一个内容完整页面的时候往往需要向服务器请求数据,所以现在服务端的逻辑变成 等待数据获取完毕,然后将页面转换成 html 字符串

其中数据获取有以下几个问题:

  1. 获取哪些数据?
  2. 如何得到获取数据的方法?
  3. 应在何时预取数据?
  4. 预取的数据应保存在哪里?
  5. 预取的数据应该怎么样跟客户端进行同步?

问题 1:
我们的数据用来渲染页面,那么我们就需要组成当前页面的所有组件各自所需要的数据

问题 2:
每个需要进行服务端数据预取的组件定义一个 asyncData 方法,此方法用于数据预取

问题 3:
我们需要先得到当前页面所有需要渲染的组件,然后再进行数据预取

问题 4:
由于还需要进行数据同步,所以很难将数据保存在组件的私有 data 上面,放在 vuex 上面是个普遍的选择

问题 5:
服务端在返回 html 字符串的时候,store 数据将被序列化以后以 window.__INITIAL_STATE__=/* store state */ 的形式插入到脚本当中被客户端获取,客户端的 store 使用 store.replaceState 方法同步 state

简单复述一下上面的流程就是:
在渲染当前页面的所有组件加载完毕以后,执行这些组件的 asyncData 方法,这些方法将获取到的数据将由 vuex 托管,获取数据完毕以后即可将应用渲染成 html 字符串,vuex store 的 state 将会被序列化以后一并传输到客户端,被客户端进行同步

下面是实现的一些细节:

  1. 判断组件加载完毕的方法是 vue-router 的 onReady 方法
  2. 获取当前页面的所有组件为 vue-router 的 getMatchedComponents 方法

由于源码太长所以没贴出来,具体可以到官网浏览

服务端数据预取的关键点算是总结的差不多了,下面简单说一下客户端的数据预取

客户端数据预取

客户端的数据预取方法可分为两种:

  1. 等待数据获取完毕后再进行视图切换
  2. 先进行视图切换然后在进行数据获取

两种方法区别在于让用户在什么时候产生等待的感觉,第一种是在页面切换时,而第二种是在页面切换完毕等待内容的出现时

第一种方法的实现使用了 vue-router 实例的 beforeResolve 方法,这个方法执行在异步组件加载完毕后,导航被确认之前,当完成数据预取以后 router 才会进行 DOM 更新等步骤

第二种方法的实现跟我们一般进行数据获取一致,在 beforeMount 钩子当中执行

SSR 在编写代码时候的限制

  1. 由于浏览器特定的 API 将会在服务端报错,如 ’document’、’window’ 等,尽量避免使用此类 API 或者在非服务端运行的声明周期函数中调用如 ’mounted’ 等等
  2. 指令由于能直接操作 DOM 会受到很大的限制

SSR 的 webpack 构建原理

以官方的 hackernews demo 为例,webpack 有两个入口 entry-cliententry-server分别负责构建客户端和服务端的文件

服务端方面 webpack 会输出一个名叫 vue-ssr-server-bundle 的 json 文件,此文件由官方提供的 VueSSRServerPlugin 插件所构建而成,是服务端的构建清单,传入 createBundleRenderer 生成服务端渲染所需要的renderer

客户端方面 webpack 输出的是由代码分割而成的 chunk 和公用 bundle,与一般单页应用的构建相似,不同的是会生成一个vue-ssr-client-manifest,此文件是客户端方面的构建清单,包含所有 chunk 的信息,将其传入上面的 renderer 当中能自动将 chunk 嵌入到 html 当中,当然用户也能够取消,自行选择手动嵌入的内容

退出移动版