mvc框架中一種優(yōu)雅的模板同步方案

雖然現(xiàn)在都提倡前后端分離、node同構(gòu)直出,但是在很多公司的舊項(xiàng)目中,基于php的mvc框架項(xiàng)目仍然很多。在這種項(xiàng)目中,前端人員需要圍繞其中的view層進(jìn)行開發(fā),一些業(yè)務(wù)數(shù)據(jù)直接在模板中獲取。
下面舉一個(gè)例子,后臺(tái)采用laravel框架,前端模版引擎為blade模板。現(xiàn)在需要編寫一個(gè)模版,這個(gè)模板有以下邏輯:如果用戶已經(jīng)登錄,提示已經(jīng)登錄,否則提示未登錄。

 <!--index.blade.php(laravel的模板文件以blade.php作為后綴)-->
...
    @if(Auth::check())
         <h1>已經(jīng)登錄</h1>
    @else
          <h1>未登錄</h1>
    @endif
...

嗯,看上去除了增加blade引擎的模板語法外,其它跟寫法跟原生前端寫法并無差別。 由于嵌套了blade語法,導(dǎo)致這部分代碼只能被blade模板引擎解析,無法被其它項(xiàng)目共用。當(dāng)別的項(xiàng)目需要實(shí)現(xiàn)相同的頁面時(shí),只能另外寫一套。比如在discuz(開源的論壇框架)的頁面中,要實(shí)現(xiàn)上述例子,需要這樣寫:

<!--index.htm(discuz的模板文件以htm作為后綴)-->
...
     <!--{if $_G['uid']}-->
         <h1>已經(jīng)登錄</h1>
    <!--{else}-->
          <h1>未登錄</h1>
    <!--{/if}-->
...

所以當(dāng)一個(gè)項(xiàng)目存在幾種模板引擎后,要實(shí)現(xiàn)模板的共用就成了一個(gè)大的問題,因?yàn)槊總€(gè)模板引擎有不同的模板語法。比如金蝶社區(qū),金蝶社區(qū)這個(gè)項(xiàng)目,一部分頁面用了laravel框架,一部分頁面用了discuz。在不同模板引擎中,有一些模版是重復(fù)出現(xiàn)的,比如頂部導(dǎo)航欄、footer等。由于業(yè)務(wù)需求不斷在變動(dòng),這些模塊一直在改變,如果仍然按照blade、discuz的語法寫兩套,維護(hù)起來勢(shì)必非常麻煩。

我們也做了一些嘗試,比如:

采用js生成html的方式

這種方式的前提是需要我們剔除后臺(tái)的模板語法,這樣才能被瀏覽器識(shí)別。然而在js中無法直接獲取后臺(tái)數(shù)據(jù),只能采用數(shù)據(jù)接口的方式異步請(qǐng)求、或者在html嵌入隱藏的input標(biāo)簽,通過id獲取input中的值來獲取后臺(tái)數(shù)據(jù)。然而這兩種方式對(duì)原有代碼的改動(dòng)非常大,還喪失了模板語法的優(yōu)勢(shì),需要額外寫dom操作,并會(huì)有種內(nèi)容加載很慢的感覺(js未引入,相關(guān)內(nèi)容就不會(huì)被渲染)。

優(yōu)雅的解決方案

為了實(shí)現(xiàn)跨項(xiàng)目的模版同步,我們?cè)谠屑軜?gòu)外增加了一個(gè)生成器。

我們?cè)O(shè)計(jì)了一個(gè)模板生成器,我們對(duì)它的期望是,在不改變?cè)械捻?xiàng)目結(jié)構(gòu)前提下,生成符合相應(yīng)模板語法的模板。它以一個(gè)源模板作為輸入,并輸出其他項(xiàng)目模板語法的模板。前端人員只需要編寫源模板,即可借助構(gòu)建工具生成編譯后的模板,并同步到項(xiàng)目中。

那具體怎么實(shí)現(xiàn)呢?下面還是以上面提到的例子作為講解。
首先在源模板文件上,應(yīng)該具有如下特點(diǎn):

可以根據(jù)不同的模板環(huán)境生成不同的模板#####

可能有點(diǎn)抽象,直接上代碼,應(yīng)該就會(huì)清晰明了了

 <!--源模板 demo.html-->
...
    **laravel?`@if(Auth::check())`:`<!--{if $_G['uid']}-->`**
    <h1>已經(jīng)登錄</h1>
    **laravel?`@else`:`<!--{else}-->`**
    <h1>未登錄</h1>
    **laravel?`@else`:`<!--{else}-->`**

...


可能你看到這里就有點(diǎn)疑惑,**號(hào)之間的內(nèi)容是什么鬼?如果去除星號(hào)后,你會(huì)發(fā)現(xiàn)這是一個(gè)三元運(yùn)算表達(dá)式!當(dāng)laravel變量為true時(shí)返回字符串@if(Auth::check()),否則返回,這樣的話,源模板就可以根據(jù)里面的變量值生成相應(yīng)的模板啦。
那你可能又會(huì)疑惑,怎么把這個(gè)變量傳進(jìn)去呢?
這里我們要寫一個(gè)renderTemplate方法:

@str:讀取文件生成的字符串
@data:傳遞進(jìn)去的變量
@callback:回調(diào)函數(shù),其中第一個(gè)參數(shù)就是編譯后的字符串
const renderTemplate = function(str,data,callback){
    var a = str.replace(/\*\*(.*?)\*\*/g,function(res){//獲取包括**的內(nèi)容
        var result = arguments[1]
        //result匹配了**之間的內(nèi)容
        try{
            with(data){
                var rel = eval(result)
                //把字符串當(dāng)作表達(dá)式輸出,并用with修改作用域
                return rel
            }
        }catch(e){
            console.log(e)
            //捕獲異常
        }
    })
    callback&&callback(a)
}

原理就是用正則匹配出**號(hào)的內(nèi)容,并將里面的字符串當(dāng)作表達(dá)式執(zhí)行。 因而出了三元表達(dá)式,立即執(zhí)行的匿名函數(shù)也可以正常執(zhí)行。比如:

...
**(function(){ return 'david'})()**
...

會(huì)輸出

...
david
...

那renderTemplate具體怎么調(diào)用呢?
讓我們寫一個(gè)node腳本:

//index.js
const {readFileSync,writeFile} = require('fs')
var file ='/Users/david/demo.html' //文件絕對(duì)路徑
var content = readFileSync(file,'utf-8')//同步讀取文件
renderTemplate(content,{laravel:true},function(html){
    console.log(html)//接下來就應(yīng)該做文件保存工作啦
})
renderTemplate(content,{laravel:false},function(html){
    console.log(html)//接下來就應(yīng)該做文件保存工作啦
})

在執(zhí)行node index.js后,如無意外,你會(huì)在控制臺(tái)得到兩條輸出

控制臺(tái)截圖

現(xiàn)在的基本邏輯已經(jīng)跑通啦,剩下的就是文件存儲(chǔ)的操作啦。

進(jìn)階使用

以上只是一個(gè)簡(jiǎn)單的demo,每一個(gè)demo都要寫對(duì)應(yīng)的腳本邏輯才能用于實(shí)際項(xiàng)目中。在實(shí)際情況中,我們希望前端專注在源模板的編寫上,所以我們把node腳本進(jìn)行封裝。由于模板生成后的路徑、文件名不定、所以我們要把這一塊設(shè)置從腳本中分離出來,針對(duì)具體的源模板配置。

下面是我分離后的目錄結(jié)構(gòu):

目錄結(jié)構(gòu)

dist:源模板輸出目錄
node_modules:模塊依賴目錄
template:源模板目錄,針對(duì)每一個(gè)源模板,配置有一個(gè)config.js和index.html
其中config.js是該源模板的相關(guān)配置參數(shù)。下面會(huì)詳細(xì)提到。
index.html是源模板文件
gulpfile.js是gulp的入口文件
package.json項(xiàng)目相關(guān)配置信息

首先查看gulpfile.js

const gulp = require('gulp')
const watch = require('gulp-watch')
const {readFileSync,writeFile} = require('fs')
const path = require('path')
gulp.task('default',['build'])
const renderTemplate = function(str,data,callback){
    var a = str.replace(/\*\*(.*?)\*\*/g,function(res){//獲取包括**的內(nèi)容
        var result = arguments[1]
        //result匹配了**之間的內(nèi)容
        try{
            with(data){
                var rel = eval(result)
                //把字符串當(dāng)作表達(dá)式輸出,并用with修改作用域
                return rel
            }
        }catch(e){
            console.log(e)
            //捕獲異常
        }
    })
    callback&&callback(a)
}

gulp.task('build',()=>{
    watch('template/**/*.html',{ ignoreInitial: false },function(event) {
        let file = event.path//文件變動(dòng)路徑
        const content = readFileSync(file,'utf-8')//同步讀取文件
        const config = require(path.resolve(file,'../config.js'))//獲取同級(jí)目錄下的config文件
        config.forEach(item=>{
            renderTemplate(content,item.data,function(str){
                writeFile(path.resolve(item.output.path,item.output.filename),str,function(err){
                    if(err){
                        console.log(err)//有錯(cuò)誤輸出錯(cuò)誤
                        process.exit()
                    }
                    console.log('File ' + item.output.filename + '編譯成功');
                })
            })
        })
    });
})

里面的注釋應(yīng)該很清楚了,然后查看demo中的config.js

module.exports = [{
    data:{
        laravel:true
    },
    output:{
        filename:'laravel_demo.blade.php',
        path:'dist'
    }
},{
    data:{
        laravel:false
    },
    output:{
        filename:'discuz_demo.blade.php',
        path:'dist'
    }
}]

這個(gè)模塊實(shí)際上返回了一個(gè)數(shù)組,數(shù)組中的每一個(gè)對(duì)象對(duì)應(yīng)輸出的相關(guān)配置。data即renderTemplate的data參數(shù)。output則記錄了輸出的文件名和路徑。(注意:這里的path最好寫成絕對(duì)路徑)

在腳本執(zhí)行后,應(yīng)該會(huì)生成兩個(gè)模板,一個(gè)為"laravel_demo.blade.php",一個(gè)為"discuz_demo.blade.php".

項(xiàng)目github地址

https://github.com/David-zzg/kfc_module_creator

結(jié)語

至此,最核心的基本功能已經(jīng)完成了。然而,這并不意味著結(jié)束。在我們引入構(gòu)建工具的同時(shí),node已經(jīng)為我們打開一扇大門。我們可以在模板的基礎(chǔ)上再加些額外處理。比如壓縮文件、比如引入posthtml-bem實(shí)現(xiàn)css的命名管理等等!

最后說一句

這是我第一次寫技術(shù)類的博客,如有錯(cuò)誤之處,請(qǐng)大大們指出!只有寫過技術(shù)博客后,我才真正理解到寫技術(shù)博客的艱辛!致敬各位寫博客的大大們!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,315評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,699評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,882評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,189評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,388評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,613評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評(píng)論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,112評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,334評(píng)論 2 377

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