實現一個簡單的模板引擎

發表於2016-11-12

簡介

模板引擎,其實就是一個根據模板和資料輸出結果的一個工具。

我們要開發一個將模板檔案轉換成我們實際要使用的內容的工具,這個工具就是模板引擎。我們把模板檔案裡的內容當成字串傳入到模板引擎中,然後模板引擎根據一定語法對該字串進行解析處理,然後返回一個函式,之後我們在執行函式時把資料傳輸進去,即可拿到根據模板和資料得到的新字串。最後我們想怎麼處理該字串就看需求了,如果用於前端模板生成的話,則可以用dom的innerHTML這個屬性來追加內容。

目前前端的模板引擎多得數不勝數,語法特性也花樣百出,用行內的話來說,我們要實現的是一種基於字串的模板引擎。

簡要概述流程如下:

優劣

  • 此模板引擎可用於任意一端,前端後端即插即用,不侷限於生成內容的語法,只要生成內容為字串文字即可。比如在合併Sprite圖工具中要根據圖片大小位置生成對應的css定位檔案,我們也可以用該引擎生成而不需要另外再寫一套引擎。
  • 此模板引擎對於資料的更改,需要重新渲染一遍模板,所以在初次渲染和之後的模板更新需要耗費同樣的資源。
  • 應用於前端時,此模板引擎依賴於innerHTML,存在注入問題。

需求

而此次,我們希望實現一個基於字串的模板引擎。提供的使用方式儘可能簡單,比如類似如下的方式:

並且希望至少提供以下四種語法:

條件判斷

陣列遍歷

變數定義

插值

 

開工

STEP 1

按照前面定下的需求,我們先實現一個對外的介面,程式碼如下:

此處,f即是我們生成的函式,而生成該函式的函式我命名為doParseTemplate,接收三個引數,content是我們輸入的模板檔案的字串內容,data是我們要傳入的資料,而filter即為模板中可傳入的過濾器。目前doParseTemplate這個函式還未實現,接下來就來實現此函式。

STEP 2

為了生成一個可用的函式,我們要通過new Function(‘DATA’, ‘FILTER’, content);這樣的方法來構造一個函式,其中content即是函式體的字串內容。

我們先設定要生成的函式f的結構如下:

事實上,註釋中處理變數、處理過濾器和處理內容這部分是由外部傳入決定的,所以只有這部分是可變的,其餘的程式碼都是固定的。為此我們可以使用陣列來存放相關的內容,然後在可變部分留一個佔位符,在解析到處理變數、處理過濾器和處理內容部分時再把語句塞進去即可。程式碼如下:

現在固定結構有了,接下來我們要處理模板相關的內容,即在放置生成器佔位符的那個位置上追加內容。首先,我們要先把輸入的變數和過濾器處理好,即在佔位符的位置加入諸如var a = 1;這樣的內容:

如上,在處理變數和過濾器時需要的值直接從傳入的DATA和FILTER變數裡獲取,需要注意的點就是過濾器我們單獨存在一個FILTERS物件裡面去,主要是為了防止傳入的FILTER物件變化帶來的一些不必要的影響。之後我們要對模板內容進行解析,鑑於程式碼越來越長,接下來直接貼上面註釋some code for parse content裡面的內容,其他部分暫且省略。

對於模板內容的解析,因為語法相對簡單,此處直接使用while迴圈遍歷。在我們上面定義的語法中,有關結構相關的語法都用{和}來包圍,插值則是${和},因此針對這兩種語法需要分開處理。整個流程的判斷如下:

  1. 搜尋語句開始符{;
  2. 判斷{前面是否有轉義符\;
  3. 搜尋語句結束符};
  4. 判斷}前面是否有轉義符\;
  5. 判斷{前面是否帶有取值符號$;
  6. 提取語句內容,即{和}裡面的內容;
  7. 將語句之前,即{或${之前未放入快取區的內容放入快取區;
  8. 解析語句,並把解析結果放入快取區;
  9. 迴圈上述1-8的過程,直到搜尋不到語句開始符{,則判斷為結尾,把剩下的內容放入快取區;
  10. 把目前快取區的的內容存到需要輸出的陣列中。

以上提到的快取區,即是上面程式碼中的out陣列。當遍歷完模板內容後,把快取區合併成一個字串,然後追加到佔位符末尾。其中關於語句的解析用到的函式transStm目前接下來將要實現。

STEP 3

transStm函式實現比較簡單,因為我們需求中設定的語法也不復雜。程式碼如下:

如上,其實只是把語句中的內容逐一用正則去匹配,當匹配到屬於某種規則的語句,則針對性處理並返回結果。比如我有一個語句{if a > 1},然後正則去匹配,會匹配出是條件判斷中的if語句,然後會處理成js程式碼if(a > 1) {並返回。而語句{/if}則會處理成}並返回。因此如下程式碼:

會處理成:

其中關於語法匹配的正則和返回處理如下:

其中reg欄位是正規表示式,若匹配成功,則執行或直接返回val欄位的值。

STEP 4

如果有仔細看前面貼出來的程式碼,發現上面有用到一個變數defaultFilter,這是用來定義模板引擎需要自帶的過濾器的。常用ejs的朋友們估計就會清楚,ejs裡就自帶了很多很實用的過濾器,我在下面例子就貼出一個常用的過濾器方法:

用法很簡單,當我們有一個變數a,內容為<div style=”color: red;”>red</div>時,因為我們經常將模板引擎生成的內容直接用innerHTML塞進節點之中,而假如我們像${a}這種方式直接使用這個變數的時候,在頁面中就只會顯示一個紅色的red。

為了防止此類注入的情況發生,我在上面實現了一個叫escape的過濾器,將使用方式改為${a|escape}就可以進行特殊符號的轉義,在頁面上直接顯示變數a的內容<div style=”color: red;”>red</div>。

尾聲

至此,一個完整的基於字串的模板引擎就完成了,上面的程式碼使用了es6語法的部分特性來編寫,如果需要相容的話可以使用babel來將程式碼轉成es5語法,在做一下壓縮混淆的話,實際的程式碼不足3k。

前面也提到過,基於字串的模板引擎最大的好處在於語法自由,你可以做到完全不需要關心模板的型別,你可以寫一個css檔案的模板,也可以寫一個html檔案的模板,只要有對應的模板就會有相應的輸出,並且前後端可以共用。

如果你想要看完整的程式碼的話,請戳這裡

相關文章