1. 程式人生 > 程式設計 >Vue SSR 即時編譯技術的實現

Vue SSR 即時編譯技術的實現

當我們在服務端渲染 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>"
 ),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 即時編譯 內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!