模版引擎,對如今的 Web 開發極爲重要,幾乎全部主流 Web 框架都會支持一種或多種模版引擎,模版引擎能夠分離用戶界面和業務邏輯,工做原理主要是一種翻譯,後端對特定的標記、語法、變量等渲染後再輸送給瀏覽器,現在模版引擎已經很是強大,在相關領域能夠幫助開發者節約不少時間精力,好比緩存、設計分層、插件化。不一樣的模版引擎變幻無窮,各類語言也是層出不窮,好比 PHP 模版引擎中的老大哥 Smarty,Python 的 Jinja2,也是 Flask 中內置的模版引擎,現在前端也有新生模版引擎,依賴前端的性能提高,像後端同樣處理模版語言渲染數據。javascript
Leaf 做爲 Vapor 官方提供的組件之一原生集成在 Vapor 中,Leaf 模版文件以 .leaf
結尾,模版語法夾雜在 HTML 之間,咱們能夠直接使用而不須要引入其餘外部依賴。html
咱們能夠在路由中進行模版的渲染,同時附帶給模版傳參數,參數以 Dict 的形式放在第二個位置,而後在 .leaf
模版文件中就能拿到對應的數據。前端
drop.get { req in
return try drop.view.make("index.leaf", ['greeting': "Hello world!"])
}複製代碼
Leaf 使用 #
做爲模版語法的標記,筆者也很苦惱爲啥 Vapor 的做者要用井號作語法標記,在 Github 也有人提出 #
會與 HTML/CSS
有衝突(issue #11)。java
咱們能夠用 #()
來輸出 HTML, 這裏須要注意的是這樣輸出 Leaf 會對其中的 HTML 進行轉義,舉個例子,以下代碼輸出到瀏覽器,HTML 源代碼真是內容實際上是:hello
,並無被 A 標籤包起來。node
#(<a>hello</a>)複製代碼
若是想輸出原始 HTML 怎麼辦呢?咱們能夠用 raw() {}
來輸出不會被轉移的 HTML,也就是說到瀏覽器會被解析成對應的 HTML 代碼,以下會在瀏覽器輸出:<a>hello</a>
。git
#raw() { <a>hello</a> }複製代碼
須要注意的是不要讓用戶輸入內容用
#raw(){ }
進行原始輸出,好比評論,以避免形成惡意代碼執行的漏洞!github
#(variable)複製代碼
#equal(leaf, leaf)複製代碼
下面這段代碼邏輯就是 if:elif:else
swift
#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("template")
#export("template") { Leaf/HTML }
#extend("template")
#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 有對應的語法高亮插件,若有須要能夠試試。
另外若是你喜歡相似於 Django 和 Mustache 式的語法,能夠看看 Stencil,也是一個純 Swift 寫的模版引擎。
這是 [Swift Web 開發之 Vapor] 系列的第三篇,說了說 Vapor 中自帶的 Leaf 模版引擎,按照筆者目前的使用狀況來看其實 Leaf 還不太成熟,雖然還有太多須要優化改進的地方,不過我相信以後必定會愈來愈好的。因此不要懼怕,趕忙來寫 Swift Server Side 吧!
以前開的坑在用 Vapor 寫一個博客程序 NSPress,若是你們有興趣歡迎討論。