用Golang渲染Vue模板

Why not Nuxt

大佬們進(jìn)來(lái)一定會(huì)有一個(gè)疑問(wèn):為什么已經(jīng)有vue-ssr了(如nuxt框架)還需要用go來(lái)渲染?vue-ssr提供的前后端同構(gòu)、單頁(yè)應(yīng)用加上vue的數(shù)據(jù)綁定功能,能少寫(xiě)很多代碼,它不香嗎?

筆者的回答是香,也不香,確實(shí)Vue用它簡(jiǎn)單易上手的特性得到了很多人喜愛(ài),也包括我,所以當(dāng)我需要服務(wù)端渲染的時(shí)候,也自然的使用了vue-ssr,選用的nuxtjs.org框架,但事物總有好有壞,很快我就發(fā)現(xiàn)了它的問(wèn)題。

性能低

如果項(xiàng)目是一個(gè)后臺(tái)管理系統(tǒng),那么首屏渲染速度和運(yùn)行時(shí)的性能可能不怎么重要,但如果是一個(gè)面向C端的網(wǎng)站,響應(yīng)速度卻十分總要,因?yàn)檫@直接影響到用戶體驗(yàn)。

SSR有一個(gè)優(yōu)點(diǎn)就是首屏直出,是不是就能解決首屏慢的問(wèn)題了呢?并不能。在vue-ssr渲染過(guò)程中,服務(wù)端渲染只是其中一半,當(dāng)首屏數(shù)據(jù)到達(dá)瀏覽器之后,為了能夠?qū)崿F(xiàn)vue的響應(yīng)式數(shù)據(jù),則還需要一步操作:客戶端激活,這一步的性能將影響什么呢?

如果客戶端激活速度過(guò)慢會(huì)發(fā)生以下問(wèn)題:

  • 用戶將先看到頁(yè)面內(nèi)容,但是會(huì)卡一小會(huì)沒(méi)響應(yīng)(如沒(méi)辦法滑動(dòng)),這是因?yàn)榭蛻舳思せ钍且粋€(gè)很耗cpu的操作。
  • 業(yè)務(wù)js執(zhí)行變慢,如懶加載、動(dòng)效代碼都會(huì)在客戶端激活完成之后才會(huì)執(zhí)行,這會(huì)導(dǎo)致用戶首先看不到圖片或者動(dòng)效,給用戶卡頓的感覺(jué),在cpu更慢的手機(jī)端尤為明顯。

客戶端激活的性能也是有辦法調(diào)優(yōu)的,比如這篇文章提到的懶激活vue-lazy-hydrationHow to Drastically Reduce Estimated Input Latency and Time to Interactive of SSR Vue.js Applications,不過(guò)也許客戶端激活的性能還不是重點(diǎn),因?yàn)榻酉聛?lái)還有Node端渲染的性能問(wèn)題。

在我參與的項(xiàng)目中,由于頁(yè)面功能復(fù)雜,一個(gè)頁(yè)面需要500ms左右的渲染時(shí)間,也由于有動(dòng)態(tài)路由參數(shù)的功能存在,沒(méi)辦法像靜態(tài)頁(yè)面一樣加上緩存,就導(dǎo)致了在并發(fā)稍微高一點(diǎn)之后,響應(yīng)速度越來(lái)越慢。

可擴(kuò)展性低

大量的代碼被封裝到了nuxt里, 過(guò)多的配置項(xiàng)被放在了nuxt.config.js中, 不夠靈活就導(dǎo)致了很多特性沒(méi)辦法實(shí)現(xiàn):

  • 如要修改head必須修改meta, 但vue-meta配置是有限的, 比如不支持meta標(biāo)簽閉合(可惡的搜狗站長(zhǎng)認(rèn)證需要閉合的meta標(biāo)簽).
  • 如publicPath無(wú)法動(dòng)態(tài)修改.

當(dāng)你想做一個(gè)更復(fù)雜的網(wǎng)站時(shí), nuxt雖然開(kāi)箱即用但卻又像一個(gè)盒子一樣讓你四處碰壁.

所以我決定放棄龐大笨重(對(duì)于我們的項(xiàng)目來(lái)說(shuō))的nuxt, 回歸字符串渲染.

思考

也許在面臨更為致命的性能問(wèn)題時(shí),什么響應(yīng)式、數(shù)據(jù)綁定功能也不再重要,我們開(kāi)始考慮傳統(tǒng)模板引擎。

我們知道傳統(tǒng)模板引擎的性能很好,因?yàn)樗麄兪腔谧址唇佣皇翘摂M節(jié)點(diǎn)再轉(zhuǎn)dom,但美中不足的是他們都不如vue模板美觀好用(就不對(duì)比JSX了,抱歉我對(duì)JSX不熟悉),可以預(yù)見(jiàn)當(dāng)項(xiàng)目復(fù)雜之后傳統(tǒng)模板的代碼將一團(tuán)糟。

正好筆者熟悉Golang和Vue,如果能讓Golang在后端發(fā)揮它的優(yōu)點(diǎn)(并發(fā)、性能),讓Vue(模板)發(fā)揮它的優(yōu)點(diǎn)(簡(jiǎn)潔、專(zhuān)業(yè)、現(xiàn)代化),何樂(lè)而不為?

難點(diǎn)

使用Go來(lái)渲染Vue模板并不容易實(shí)現(xiàn),隨便一想便知道其中的難點(diǎn):

  • 解析vue各種語(yǔ)法(如slot、v-if、v-for)并一一實(shí)現(xiàn),這可能不復(fù)雜,但工作量很大。
  • 解析js表達(dá)式,在模板中會(huì)大量使用到j(luò)s表達(dá)式,如v-if = "a != 0",現(xiàn)在需要使用Go去計(jì)算這些表達(dá)式,雖然知道有AST(抽象語(yǔ)法樹(shù)) 這是可行的,但工作量也很大。
  • 生成Go代碼,為了減少運(yùn)行時(shí)損耗,和webpack打包原理一樣,我們需要提前對(duì)代碼進(jìn)行處理,也就是生成中間代碼。和vue-loader類(lèi)似,在這個(gè)項(xiàng)目中,需要我們從Vue模板生成render函數(shù),不同的是我們的render函數(shù)是Golang語(yǔ)言的。

不過(guò)既然都是可行的,不妨試試。

制作

從構(gòu)建一個(gè)最小化模型開(kāi)始,我們要渲染的模板是這個(gè)樣子的

<template>
  <div>
    <span class="bg-gray" :class="cus_class" :style="{'font-size': fontSize+'px'}"> {{msg}} </span>
  </div>
</template>

我們將這個(gè)組件命名為消息提示組件,它可能是這個(gè)樣子


element-ui alert

1. 解析html成節(jié)點(diǎn)樹(shù)

解析html比我想象中復(fù)雜,這是因?yàn)橛凶蚤]合和不閉合的標(biāo)簽,如<meta charset="UTF-8">,如果使用xml的處理邏輯的話需要做很多額外判斷,為了不重復(fù)造輪子,最終選用golang.org/x/net/html包來(lái)解析html,不過(guò)值得注意的是正規(guī)的html格式有一些要求:如select里只能包含option子節(jié)點(diǎn),但Vue模板由于有自定義組件和slot語(yǔ)法等,可能不滿足html的要求,這會(huì)讓html包無(wú)法正確解析出節(jié)點(diǎn),由于沒(méi)有更好的解析包作為代替,無(wú)奈只好魔改一點(diǎn)html包了,改好的代碼在項(xiàng)目里,可以翻到文末查閱。

2. 解析vue模板語(yǔ)法

這一步十分簡(jiǎn)單,我們只需要遞歸遍歷html節(jié)點(diǎn)數(shù)中的節(jié)點(diǎn),根據(jù)節(jié)點(diǎn)的attr,再生成一個(gè)vue節(jié)點(diǎn)結(jié)構(gòu)體,其中包含如porps,v-if等信息。這一步是為了方便的從節(jié)點(diǎn)樹(shù)生成Golang代碼。

3. 生成Go代碼

遞歸節(jié)點(diǎn)

我們需要根據(jù)節(jié)點(diǎn)生成Go代碼,特別要處理的是vue的各個(gè)指令,如v-if需要生成如下的Go代碼

var s = ""
if xxx {
  s = "<div></div>"
} else {
  s = "text"
}
retun s

v-for如下

var s = ""

for i, v := range arr{
  s+=  "<div></div>"
}
return s

這里不難,唯一難點(diǎn)是v-if/v-else/v-else-if的關(guān)聯(lián)關(guān)系,我也是參考vue官方的模板處理方法才實(shí)現(xiàn)的。

解析 Js AST

在v-if或者{{}}中需要使用一些js表達(dá)式,如 v-if="a!=b && a!=c",幸運(yùn)的是Golang有一個(gè)庫(kù)可以解析JS AST: https://github.com/robertkrimen/otto, 唯一不足就是只支持ES5, 不過(guò)ES5在模板中足夠了.

得到Js AST之后就需要將AST翻譯成Golang,難度不大,多寫(xiě)幾個(gè)switch case就好。代碼在此

最終生成的Go代碼會(huì)像這樣:

// Code generated by go-vue-ssr: https://github.com/zbysir/go-vue-ssr
// src_hash:535087cd1e2031e7772d0d62e5390830

package main

func (r *Render) Component_info(options *Options) string {
    this := extendMap(r.Prototype, options.Props)
    _ = this
    return r.tag("div", true, &Options{
        Style: map[string]string{"text-align": "center"},
        Slot: map[string]NamedSlotFunc{"default": func(props map[string]interface{}) string {
            return "<p style=\"padding: 10px 0; \"" + mixinAttr(nil, nil, map[string]interface{}{"height": interfaceAdd(lookInterface(this, "height"), 1)}) + ">" + interfaceToStr(lookInterface(this, "slogan"), true) + "</p><img" + mixinAttr(nil, map[string]string{"alt": "todo logo", "height": "50px"}, map[string]interface{}{"src": lookInterface(this, "logo")}) + "></img>"
        }},
        P:    options,
        Data: this,
    })
}

現(xiàn)在只需要調(diào)用則可以返回html字符串

r := NewRender()
htmlStr := r.Component_info(&Options{
        Props: map[string]interface{}{
            "title":  "go-vue-ssr",
            "slogan": "Hey vue go",
            "info": map[string]interface{}{
                "author": "bysir",
                "Hey vue go":"Hey vue go",
            },
            "logo":   "https://avatars2.githubusercontent.com/u/13434040?s=88&v=4",
            "height": 100.1,
        },
    })

結(jié)果

項(xiàng)目已經(jīng)開(kāi)源,希望能讓喜愛(ài)Vue和Go的伙伴們多一個(gè)可嘗試的東西,同時(shí)也感謝你的ISSUE。

目前已經(jīng)運(yùn)行在公司項(xiàng)目中,你可以訪問(wèn)http://zhuzi.com.cn查看渲染效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 基于Vue的一些資料 內(nèi)容 UI組件 開(kāi)發(fā)框架 實(shí)用庫(kù) 服務(wù)端 輔助工具 應(yīng)用實(shí)例 Demo示例 element★...
    嘗了又嘗閱讀 1,297評(píng)論 0 1
  • UI組件 element- 餓了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的組件庫(kù) m...
    柴東啊閱讀 15,965評(píng)論 2 140
  • UI組件 element- 餓了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的組件庫(kù) m...
    小姜先森o0O閱讀 10,136評(píng)論 0 72
  • UI組件 element- 餓了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的組件庫(kù) m...
    王喂馬_閱讀 6,597評(píng)論 1 77
  • UI組件 element- 餓了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的組件庫(kù) m...
    你猜_3214閱讀 11,350評(píng)論 0 118

友情鏈接更多精彩內(nèi)容