Vue SSR 即时编译技术的实现


Posted in Javascript onMay 06, 2020

当我们在服务端渲染 Vue 应用时,无论服务器执行多少次渲染,大部分 VNode 渲染出的字符串是不变的,它们有一些来自于模板的静态 html,另一些则来自模板动态渲染的节点(虽然在客户端动态节点有可能会变化,但是在服务端它们是不变的)。将这两种类型的节点提取出来,仅在服务端渲染真正动态的节点(serverPrefetch 预取数据相关联的节点),可以显著的提升服务端的渲染性能。

提取模板中静态的 html 只需在编译期对模板结构做解析,而判断动态节点在服务端渲染阶段是否为静态,需在运行时对 VNode 做 Diff,将动态节点转化成静态 html 需要修改渲染函数的源代码,我们将这种在运行时优化服务端渲染函数的技术称作 SSR 即时编译技术(JIT)。

GitHub:vue-ssr-jit

JIT Diff 算法

首要面对的问题是如何 Diff,完成这项工作需要两个 VNode,其中一个通过 serverPrefetch / asyncData 载入动态数据,我们称之为 Dynamic VNode,另一个未载入任何数据,我们称之为 Static VNode。我们做了一个大胆的假设,对任何用户来说,Static VNode 渲染出的 html 是一致的,并且 Static VNode 是 Dynamic VNode 的子集,不同用户的差异点在 Static VNode 相对 Dynamic VNode 的补集当中。

Vue SSR 即时编译技术的实现

以上假设对绝大部分的 Web 应用都是成立的,某些意料之外的情况将在文末做讨论

Diff 的核心在于从 Staitc VNode 中标记 Dynamic VNode,下一次仅渲染被标记的 Dynamic VNode,Diff 算法的技术示意图如下所示

Vue SSR 即时编译技术的实现

优化前的 Dynamic VNode 渲染流程图如下

Vue SSR 即时编译技术的实现

优化后的 Dynamic VNode 渲染流程图如下

Vue SSR 即时编译技术的实现

如何修改渲染函数的源代码

修改渲染函数的难点在于如何建立 VNode 与源代码的对应关系,否则我们无从得知需要优化的节点是哪段代码生成的,这看起来非常困难。幸运的是 Vue 的模板语法提供了很不错的约束,内置的编译引擎也确保了渲染函数代码结构可预测。

如下模板代码编译生成的渲染函数结构是有章可循的

<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 文件。

如下流程图演示了修改渲染函数源代码的过程

Vue SSR 即时编译技术的实现

一个简单的例子如下

<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

不要在服务端渲染周期内使用 cookie,除非你确定此数据与用户无关。可以在 serverPrefetch / asyncData 方法内使用 cookie,服务端渲染周期结束后也可以被使用,例如:mountedupdated 等等。

不推荐用法

data() {
 let cookie = cookie;
 try {
  cookie = document.cookie;
 } catch(e) {
  cookie = global.xxx.cookie;
 }
 return {
  cookie
 };
},

推荐用法

mounted() {
 this.cookie = document.cookie;
},

v-for

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>

到此这篇关于Vue SSR 即时编译技术的实现的文章就介绍到这了,更多相关Vue SSR 即时编译 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
利用javascript解决图片缩放及其优化的代码
May 23 Javascript
jquery操作checkbox实现全选和取消全选
May 02 Javascript
jQuery判断指定id的对象是否存在的方法
May 22 Javascript
javascript实现对表格元素进行排序操作
Nov 18 Javascript
JS结合bootstrap实现基本的增删改查功能
Jul 22 Javascript
Express URL跳转(重定向)的实现方法
Apr 07 Javascript
使用ng-packagr打包Angular的方法示例
Sep 21 Javascript
Vue.js 父子组件通信的十种方式
Oct 30 Javascript
微信小程序实现九宫格抽奖
Apr 15 Javascript
jQuery+ajax实现批量删除功能完整示例
Jun 06 jQuery
JavaScript 如何在浏览器中使用摄像头
Dec 02 Javascript
vue中this.$http.post()跨域和请求参数丢失的解决
Apr 08 Vue.js
深入webpack打包原理及loader和plugin的实现
May 06 #Javascript
将Vue组件库更换为按需加载的方法步骤
May 06 #Javascript
让IDE识别webpack的别名alias的实现方法
May 06 #Javascript
JS 设计模式之:工厂模式定义与实现方法浅析
May 06 #Javascript
JS 设计模式之:单例模式定义与实现方法浅析
May 06 #Javascript
基于vue3.0.1beta搭建仿京东的电商H5项目
May 06 #Javascript
JavaScript布尔运算符原理使用解析
May 06 #Javascript
You might like
PHP编程 SSO详细介绍及简单实例
2017/01/13 PHP
关于文本框的一些限制控制总结~~
2010/04/15 Javascript
jQuery Jcrop插件实现图片选取功能
2011/11/23 Javascript
Knockoutjs的环境搭建教程
2012/11/26 Javascript
jquery获得keycode的示例代码
2013/12/30 Javascript
jQuery html()方法使用不了无法显示内容的问题
2014/08/06 Javascript
JS是按值传递还是按引用传递
2015/01/30 Javascript
jQuery EasyUI基础教程之EasyUI常用组件(推荐)
2016/07/15 Javascript
Bootstrap实现基于carousel.js框架的轮播图效果
2017/05/02 Javascript
Vue2.0父组件与子组件之间的事件发射与接收实例代码
2017/09/19 Javascript
JavaScript类数组对象转换为数组对象的方法实例分析
2018/07/24 Javascript
深入理解Node内建模块和对象
2019/03/12 Javascript
JavaScript模块管理的简单实现方式详解
2019/06/15 Javascript
vue中在vuex的actions中请求数据实例
2019/11/08 Javascript
vue实现在进行增删改操作后刷新页面
2020/08/05 Javascript
[01:17:47]TNC vs VGJ.S 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
vc6编写python扩展的方法分享
2014/01/17 Python
Python 创建子进程模块subprocess详解
2015/04/08 Python
Python使用修饰器执行函数的参数检查功能示例
2017/09/26 Python
python操作xlsx文件的包openpyxl实例
2018/05/03 Python
python实现Zabbix-API监控
2018/09/17 Python
django项目搭建与Session使用详解
2018/10/10 Python
解决django后台样式丢失,css资源加载失败的问题
2019/06/11 Python
python打包成so文件过程解析
2019/09/28 Python
python 爬虫 实现增量去重和定时爬取实例
2020/02/28 Python
python中get和post有什么区别
2020/06/19 Python
python3列表删除大量重复元素remove()方法的问题详解
2021/01/04 Python
html5 http的轮询和Websocket原理
2018/10/19 HTML / CSS
环法自行车赛官方商店:Le Tour de France
2017/08/27 全球购物
Turnbull & Asser官网:英国皇室御用的顶级定制衬衫
2019/01/31 全球购物
JSP和EJB可以共享HttpSession么?EJB里面可以改变session里面的内容
2013/06/05 面试题
执行力心得体会
2013/12/31 职场文书
卫校中专生的自我评价
2014/01/15 职场文书
4s店市场专员岗位职责
2014/04/09 职场文书
四川省传达学习贯彻党的群众路线教育实践活动总结大会精神新闻稿
2014/10/26 职场文书
Python OpenCV实现传统图片格式与base64转换
2021/06/13 Python