用Golang渲染Vue模板

Why not Nuxt

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

筆者的回答是香,也不香,確實(shí)Vue用它簡(jiǎn)單易上手的特性得到了很多人喜愛阳啥,也包括我,所以當(dāng)我需要服務(wù)端渲染的時(shí)候财喳,也自然的使用了vue-ssr察迟,選用的nuxtjs.org框架,但事物總有好有壞耳高,很快我就發(fā)現(xià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)就是首屏直出误证,是不是就能解決首屏慢的問題了呢继薛?并不能。在vue-ssr渲染過程中雷厂,服務(wù)端渲染只是其中一半,當(dāng)首屏數(shù)據(jù)到達(dá)瀏覽器之后叠殷,為了能夠?qū)崿F(xiàn)vue的響應(yīng)式數(shù)據(jù)改鲫,則還需要一步操作:客戶端激活,這一步的性能將影響什么呢林束?

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

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

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

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

可擴(kuò)展性低

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

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

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

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

思考

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

我們知道傳統(tǒng)模板引擎的性能很好痪欲,因?yàn)樗麄兪腔谧址唇佣皇翘摂M節(jié)點(diǎn)再轉(zhuǎn)dom,但美中不足的是他們都不如vue模板美觀好用(就不對(duì)比JSX了攻礼,抱歉我對(duì)JSX不熟悉)业踢,可以預(yù)見當(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)潔、專業(yè)雇锡、現(xiàn)代化)逛钻,何樂而不為?

難點(diǎn)

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

  • 解析vue各種語法(如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(抽象語法樹) 這是可行的旺订,但工作量也很大。
  • 生成Go代碼超燃,為了減少運(yùn)行時(shí)損耗耸峭,和webpack打包原理一樣,我們需要提前對(duì)代碼進(jìn)行處理淋纲,也就是生成中間代碼劳闹。和vue-loader類似,在這個(gè)項(xiàng)目中洽瞬,需要我們從Vue模板生成render函數(shù)本涕,不同的是我們的render函數(shù)是Golang語言的伙窃。

不過既然都是可行的晦闰,不妨試試呻右。

制作

從構(gòu)建一個(gè)最小化模型開始声滥,我們要渲染的模板是這個(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)樹

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

2. 解析vue模板語法

這一步十分簡(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)樹生成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è)庫可以解析JS AST: https://github.com/robertkrimen/otto, 唯一不足就是只支持ES5, 不過ES5在模板中足夠了.

得到Js AST之后就需要將AST翻譯成Golang,難度不大疟赊,多寫幾個(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)開源,希望能讓喜愛Vue和Go的伙伴們多一個(gè)可嘗試的東西近哟,同時(shí)也感謝你的ISSUE驮审。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末熙掺,一起剝皮案震驚了整個(gè)濱河市未斑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌币绩,老刑警劉巖蜡秽,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異缆镣,居然都是意外死亡芽突,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門董瞻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寞蚌,“玉大人,你說我怎么就攤上這事力细〔窃瑁” “怎么了固额?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵眠蚂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我斗躏,道長(zhǎng)逝慧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任啄糙,我火速辦了婚禮笛臣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隧饼。我一直安慰自己沈堡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布燕雁。 她就那樣靜靜地躺著诞丽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拐格。 梳的紋絲不亂的頭發(fā)上僧免,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音捏浊,去河邊找鬼懂衩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浊洞。 我是一名探鬼主播牵敷,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼法希!你這毒婦竟也來了劣领?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤铁材,失蹤者是張志新(化名)和其女友劉穎尖淘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體著觉,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡村生,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饼丘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趁桃。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肄鸽,靈堂內(nèi)的尸體忽然破棺而出卫病,到底是詐尸還是另有隱情,我是刑警寧澤典徘,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布蟀苛,位于F島的核電站,受9級(jí)特大地震影響逮诲,放射性物質(zhì)發(fā)生泄漏帜平。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一梅鹦、第九天 我趴在偏房一處隱蔽的房頂上張望裆甩。 院中可真熱鬧,春花似錦齐唆、人聲如沸嗤栓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茉帅。三九已至,卻和暖如春媒殉,著一層夾襖步出監(jiān)牢的瞬間担敌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工廷蓉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留全封,地道東北人马昙。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像刹悴,于是被迫代替她去往敵國(guó)和親行楞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

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