Swift Web 開發之 Vapor – 模版 Leaf(三)

isaced發表於2019-01-28

Swift Web 開發之 Vapor – 模版 Leaf(三)

模版引擎,對現在的 Web 開發極為重要,幾乎所有主流 Web 框架都會支援一種或多種模版引擎,模版引擎可以分離使用者介面和業務邏輯,工作原理主要是一種翻譯,後端對特定的標記、語法、變數等渲染後再輸送給瀏覽器,如今模版引擎已經非常強大,在相關領域可以幫助開發者節約很多時間精力,比如快取、設計分層、外掛化。不同的模版引擎千變萬化,各種語言也是層出不窮,比如 PHP 模版引擎中的老大哥 Smarty,Python 的 Jinja2,也是 Flask 中內建的模版引擎,如今前端也有新生模版引擎,依賴前端的效能提升,像後端一樣處理模版語言渲染資料。

Leaf 作為 Vapor 官方提供的元件之一原生整合在 Vapor 中,Leaf 模版檔案以 .leaf 結尾,模版語法夾雜在 HTML 之間,我們可以直接使用而不需要引入其他外部依賴。

渲染

我們可以在路由中進行模版的渲染,同時附帶給模版傳引數,引數以 Dict 的形式放在第二個位置,然後在 .leaf 模版檔案中就能拿到對應的資料。

drop.get { req in
    return try drop.view.make("index.leaf", [`greeting`: "Hello world!"])
}複製程式碼

標記 (#)

Leaf 使用 # 作為模版語法的標記,筆者也很苦惱為啥 Vapor 的作者要用井號做語法標記,在 Github 也有人提出 # 會與 HTML/CSS 有衝突(issue #11)。

我們可以用 #() 來輸出 HTML, 這裡需要注意的是這樣輸出 Leaf 會對其中的 HTML 進行轉義,舉個例子,如下程式碼輸出到瀏覽器,HTML 原始碼真是內容其實是:hello ,並沒有被 A 標籤包起來。

#(<a>hello</a>)複製程式碼

如果想輸出原始 HTML 怎麼辦呢?我們可以用 raw() {} 來輸出不會被轉移的 HTML,也就是說到瀏覽器會被解析成對應的 HTML 程式碼,如下會在瀏覽器輸出:<a>hello</a>

#raw() { <a>hello</a> }複製程式碼

需要注意的是不要讓使用者輸入內容用 #raw(){ } 進行原始輸出,比如評論,以免造成惡意程式碼執行的漏洞!

變數

#(variable)複製程式碼

比較

#equal(leaf, leaf)複製程式碼

判斷

下面這段程式碼邏輯就是 if:elif:else

#if(entering) {
  Hello, there!
} ##if(leaving) {
  Goodbye!
} ##else() {
  I`ve been here the whole time.
}複製程式碼

迴圈

#loop(users, "user") {
  Hello, #(user.name)! </br >
}複製程式碼

基本迴圈用 #loop ,第一個引數傳入陣列,第二個引數寫出迴圈內部的例項變數名,假設我的 users 內容是 [“Objective-C”、”Swift”、”Vapor”],輸出內容在瀏覽器看起來就會是這個樣子:

Hello, Objective-C!

Hello, Swift!

Hello, Vapor!

模組化

我們構建一些多頁面 Web 程式,常常會有每個頁面或者一種頁面部分相似的內容,比如網站的頭部、腳部,頭部一般用來放導航欄,腳部放一些版權宣告、三方連結等等,那麼在整個網站任何頁面中這兩部分的內容基本都是一致的,那麼我們可以把這兩部分的內容抽出一個單獨的 .leaf 檔案存放,並在需要的地方引入進來就可以了。

Leaf 提供了四個方法來引入/包含其他模版:

  • Import: #import("template")
  • Export: #export("template") { Leaf/HTML }
  • Extend: #extend("template")
  • Embed: #embed("template")

這四個標籤都不需要補全 .leaf 字尾,Leaf 會自動查詢對應檔案。

#import() 用來宣告一個插入點在當前模版。

#extend() 用來繼承一個模版,使用模版的內容,然後就只能#export() 填充之前宣告的插入點。

#embed("body") 才是用來引入一個模版的內容到當前位置。

舉個例子 ?:

/// base.leaf
<html>#import("html-content")</html>

/// index.leaf
#extend("base")
#export("html-content") {
    Hello
}複製程式碼

渲染 index.leaf 的內容如下: <html>Hello</html>index.leaf 頁繼承了 base.leaf 的內容, 並把 Hello 這個字串被插入到了 <html> 標記中間。注意之前的描述,繼承之後只能使用 #export() 填充,也就是在 index.leaf 檔案中其他地方任何內容將不會被渲染。

然後是 #embed() 的用法,可以直接引入另一個檔案:

/// html-content.leaf
Hello

/// index.leaf
<html>#embed("html-content")</html>複製程式碼

上面程式碼渲染 index.leaf 後的結果和之前是一樣的。

自定義標籤

有時候官方提供的標籤功能不夠我們使用,當然 Leaf 給我們留出了自定義的空間,我們可以宣告自己的標籤然後在 .leaf 模版種使用,就像 #equal()#import() 一樣。

我們可以通過宣告一個類來自定義一種標籤,借用一下官方的例子,定義在 Leaf/Tag/Models/Index.swift:,該程式碼定義了一個方法用來獲取陣列中指定下標的一個元素:

class Index: BasicTag {
    let name = "index1"

    func run(arguments: [Argument]) throws -> Node? {
        guard
            arguments.count == 2,
            let array = arguments[0].value?.nodeArray,
            let index = arguments[1].value?.int,
            index < array.count
            else { return nil }
        return array[index]
    }
}複製程式碼

然後再向 droplet 註冊它:

if let leaf = drop.view as? LeafRenderer {
    leaf.stem.register(Index())
}複製程式碼

有了自定義標籤的功能,我們可以豐富很多自己的功能邏輯,甚至定義一個快速解析 Markdown 的標籤,用起來會相當方便。

語法高亮

根據 Vapor 官方提示,VSCode 和 Atom 有對應的語法高亮外掛,如有需要可以試試。

  • Visual Studio Code – html-leaf
  • Atom – language-leaf
  • Xcode – 目前沒有很好支援的外掛,不過可以設定 Xcode 編輯器語法高亮為 HTML,稍會有改善

結語

另外如果你喜歡類似於 Django 和 Mustache 式的語法,可以看看 Stencil,也是一個純 Swift 寫的模版引擎。

這是 [Swift Web 開發之 Vapor] 系列的第三篇,說了說 Vapor 中自帶的 Leaf 模版引擎,按照筆者目前的使用情況來看其實 Leaf 還不太成熟,雖然還有太多需要優化改進的地方,不過我相信之後一定會越來越好的。所以不要害怕,趕緊來寫 Swift Server Side 吧!

之前開的坑在用 Vapor 寫一個部落格程式 NSPress,如果大家有興趣歡迎討論。

相關文章