GitHub:vue-ssr-jit当我们在服务端渲染Vue应用时,无论服务端渲染多少次,VNode渲染出来的字符串绝大部分是不变的,并且其中一些来自模板的静态html,另一些来自模板动态渲染的节点(虽然动态节点在客户端可能会发生变化,但在服务器端保持不变)。提取这两类节点,只在服务器端渲染真正动态的节点(与serverPrefetch预取数据关联的节点),可以显着提高服务器端的渲染性能。提取模板中的静态html,只需要在编译时分析模板结构,在服务端渲染阶段判断动态节点是否静态,需要在运行时对VNode做一次Diff,需要修改渲染函数,将动态节点转为静态html,我们把这种在运行时优化服务端渲染函数的技术称为SSR即时编译技术(JIT)。JITDiff算法面临的第一个问题是如何进行Diff。完成这个工作需要两个VNode,一个通过serverPrefetch/asyncData加载动态数据,我们称之为DynamicVNode,另一个不加载任何数据,我们称之为StaticVNode。我们做了一个大胆的假设,对于任何用户来说,StaticVNode渲染出来的html都是一致的,StaticVNode是DynamicVNode的一个子集。不同用户的区别在于StaticVNode相对于DynamicVNode的补充。上述假设适用于大多数Web应用程序。一些意想不到的情况将在文章末尾讨论。Diff的核心是从StaitcVNode中标记出DynamicVNode,下次只渲染标记过的DynamicVNode。Diff算法优化前的动态VNode技术示意图如下优化后的动态VNode渲染流程图如下如何修改渲染函数的源码修改渲染函数的难点在于如何建立VNode与源代码之间的对应关系,否则我们无法知道是哪一段代码生成了需要优化的节点,这看起来非常困难。幸运的是,Vue的模板语法提供了非常好的约束,内置的编译引擎也保证了渲染函数代码结构的可预测性。以下模板代码编译生成的渲染函数结构有规律可循
_c("div",[_c("static-view"),_c("dynamic-view")],1)执行_c(xxx)会生成一个VNode节点,解析_c(xxx)会生成一个结构固定的AST,结合AST和VNodeBinding,如果当前VNode是一个静态节点,修改对应的AST,然后在VNode树遍历之后将AST转化为可执行代码,代码中会有我们对VNode的优化。详细的技术实现请参考项目中的patch.js和patch-context.js文件。下面的流程图演示了修改渲染函数源码的过程。一个简单的例子如下{{name}}
官方编译生成的代码:_c("div",[_c("router-link",{attrs:{to:"/"}},[_vm._v(_vm._s(_vm.name))]),_c("router-view")],1)使用SSR编译生成的代码及时:_c("div",[_vm._ssrNode("vue-ssr-jit"),_c("路由器视图")],1);使用方法npminstall--savevue-ssr-jitconst{createBundleRenderer}=require('vue-ssr-jit')createBundleRenderer与官方函数同名接口一致。参考vuessr指南推荐使用serverPrefetch预取数据,也支持使用asyncData预取数据。哪些场景会导致优化失败请参考demo。除非您确定此数据与用户无关,否则请勿在服务器呈现周期中使用cookie。cookies可以在serverPrefetch/asyncData方法中使用,也可以在服务端渲染周期结束后使用,如:mounted、updated等。不推荐使用data(){letcookie=cookie;尝试{cookie=document.cookie;}catch(e){cookie=global.xxx.cookie;}return{cookie};},推荐用法mounted(){this.cookie=document.cookie;},v-forv-for指令建议单独包裹dom元素,不建议和其他组件并排使用,因为for循环会打乱与dom元素的对应关系抽象语法树和VNode节点,除非v-for指令在节点的整个节点层次结构是静态的,否则包含v-for指令的层次结构和子层次将不会被优化。不推荐使用