GitHub: vue-ssr-jithtml
当咱们在服务端渲染 Vue 应用时,不管服务器执行多少次渲染,大部分 VNode 渲染出的字符串是不变的,它们有一些来自于模板的静态 html,另外一些则来自模板动态渲染的节点(虽然在客户端动态节点有可能会变化,可是在服务端它们是不变的)。将这两种类型的节点提取出来,仅在服务端渲染真正动态的节点(serverPrefetch 预取数据相关联的节点),能够显著的提高服务端的渲染性能。
提取模板中静态的 html 只需在编译期对模板结构作解析,而判断动态节点在服务端渲染阶段是否为静态,需在运行时对 VNode 作 Diff,将动态节点转化成静态 html 须要修改渲染函数的源代码,咱们将这种在运行时优化服务端渲染函数的技术称做 SSR 即时编译技术(JIT)。vue
首要面对的问题是如何 Diff,完成这项工做须要两个 VNode,其中一个经过 serverPrefetch / asyncData 载入动态数据,咱们称之为 Dynamic VNode,另外一个未载入任何数据,咱们称之为 Static VNode。咱们作了一个大胆的假设,对任何用户来讲,Static VNode 渲染出的 html 是一致的,而且 Static VNode 是 Dynamic VNode 的子集,不一样用户的差别点在 Static VNode 相对 Dynamic VNode 的补集当中。git
以上假设对绝大部分的 Web 应用都是成立的,某些意料以外的状况将在文末作讨论
Diff 的核心在于从 Staitc VNode 中标记 Dynamic VNode,下一次仅渲染被标记的 Dynamic VNode,Diff 算法的技术示意图以下所示github
优化前的 Dynamic VNode 渲染流程图以下算法
优化后的 Dynamic VNode 渲染流程图以下npm
修改渲染函数的难点在于如何创建 VNode 与源代码的对应关系,不然咱们无从得知须要优化的节点是哪段代码生成的,这看起来很是困难。幸运的是 Vue 的模板语法提供了很不错的约束,内置的编译引擎也确保了渲染函数代码结构可预测。api
以下模板代码编译生成的渲染函数结构是有章可循的服务器
<template> <div> <static-view/> <dynamic-view/> </div> </template>
_c("div", [ _c("static-view"), _c("dynamic-view") ], 1)
执行 _c(xxx)
会生成一个 VNode 节点,解析 _c(xxx)
会生成一个固定结构的 AST,将 AST 与 VNode 作绑定,若是当前 VNode 为静态节点,则修改对应的 AST,VNode 树遍历结束后再将 AST 转化成可执行的代码,代码里便有了咱们对 VNode 作的优化。详细的技术实现可参考项目中的 patch.js 和 patch-context.js 文件。cookie
以下流程图演示了修改渲染函数源代码的过程闭包
一个简单的例子以下
<template> <div> <router-link to="/">{{name}}</router-link> <router-view></router-view> </div> </template> <script> export default { data() { return { name: 'vue-ssr-jit' } } } </script>
官方编译器生成的代码:
_c("div", [ _c("router-link", {attrs: { to: "/" }}, [ _vm._v(_vm._s(_vm.name)) ]), _c("router-view") ], 1)
使用 SSR 即时编译生成的代码:
_c("div", [ _vm._ssrNode( "<a href=\"/\" class=\"router-link-active\">vue-ssr-jit</a>" ), _c("router-view") ], 1);
npm install --save vue-ssr-jit
const { createBundleRenderer } = require('vue-ssr-jit')
createBundleRenderer
与官方同名函数接口一致,参考 vue ssr 指南
推荐使用 serverPrefetch 预取数据,也支持使用 asyncData 预取数据,参考 demo
不要在服务端渲染周期内使用 cookie,除非你肯定此数据与用户无关。能够在 serverPrefetch / asyncData 方法内使用 cookie,服务端渲染周期结束后也能够被使用,例如:mounted
,updated
等等。
不推荐用法
data() { let cookie = cookie; try { cookie = document.cookie; } catch(e) { cookie = global.xxx.cookie; } return { cookie }; },
推荐用法
mounted() { this.cookie = document.cookie; },
v-for 指令建议用 dom 元素单独包裹,不建议和其余组件并排使用,因为 for 循环会扰乱抽象语法树与 VNode 节点的对应关系,除非 v-for 指令所在的整个节点层级全为静态,不然将不会对包含 v-for 指令的层级及子级作优化。
不推荐用法
<template> <div> <div v-for="item in items" :key="item.id">{{item.value}}</div> <static-view></static-view> </div> </template>
推荐用法
<template> <div> <div> <div v-for="item in items" :key="item.id">{{item.value}}</div> </div> <static-view></static-view> </div> </template>
某些场景下,渲染函数引用了闭包变量,同时这个闭包变量又影响着一个动态的节点,经过 ast 逆向生成的渲染函数暂时没法追踪到以前的闭包引用,执行时会因找不到变量而报错,碰到这种状况,解析引擎将放弃当前组件的 ast 优化,转而使用优化前的渲染函数。
不推荐用法:
<template> <img :src="require(`@/assets/${img}`)" > </template>
推荐用法:
<template> <img :src="getImgUrl(img)" > </template>