Vue SSR 即時編譯技術

GitHub: vue-ssr-jithtml

當咱們在服務端渲染 Vue 應用時,不管服務器執行多少次渲染,大部分 VNode 渲染出的字符串是不變的,它們有一些來自於模板的靜態 html,另外一些則來自模板動態渲染的節點(雖然在客戶端動態節點有可能會變化,可是在服務端它們是不變的)。將這兩種類型的節點提取出來,僅在服務端渲染真正動態的節點(serverPrefetch 預取數據相關聯的節點),能夠顯著的提高服務端的渲染性能。

提取模板中靜態的 html 只需在編譯期對模板結構作解析,而判斷動態節點在服務端渲染階段是否爲靜態,需在運行時對 VNode 作 Diff,將動態節點轉化成靜態 html 須要修改渲染函數的源代碼,咱們將這種在運行時優化服務端渲染函數的技術稱做 SSR 即時編譯技術(JIT)。vue

JIT Diff 算法

首要面對的問題是如何 Diff,完成這項工做須要兩個 VNode,其中一個經過 serverPrefetch / asyncData 載入動態數據,咱們稱之爲 Dynamic VNode,另外一個未載入任何數據,咱們稱之爲 Static VNode。咱們作了一個大膽的假設,對任何用戶來講,Static VNode 渲染出的 html 是一致的,而且 Static VNode 是 Dynamic VNode 的子集,不一樣用戶的差別點在 Static VNode 相對 Dynamic VNode 的補集當中。git

complementary-set.png

以上假設對絕大部分的 Web 應用都是成立的,某些意料以外的狀況將在文末作討論

Diff 的核心在於從 Staitc VNode 中標記 Dynamic VNode,下一次僅渲染被標記的 Dynamic VNode,Diff 算法的技術示意圖以下所示github

diff.gif

優化前的 Dynamic VNode 渲染流程圖以下算法

before.gif

優化後的 Dynamic VNode 渲染流程圖以下npm

after.gif

如何修改渲染函數的源代碼

修改渲染函數的難點在於如何創建 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.jspatch-context.js 文件。cookie

以下流程圖演示了修改渲染函數源代碼的過程閉包

ast.png

一個簡單的例子以下

<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>
相關文章
相關標籤/搜索