前面的話
為什么要引入pug,pug有什么特別之處呢?有一些嵌套層次較深的頁面,可能會出現巢狀嵌套,如下圖所示

在后期維護和修改時,一不小心少了一個尖括號,或者某個標簽的開始和閉合沒有對應上,就會導致DOM結構的混亂甚至是錯誤。所以,有人發明了HAML,它最大的特色就是使用縮進排列來代替成對標簽。受HAML的啟發,pug進行了javascript的實現。
Pug原名不叫Pug,是大名鼎鼎的jade,后來由于商標的原因,改為Pug,哈巴狗。其實只是換個名字,語法都與jade一樣。丑話說在前面,Pug有它本身的缺點——可移植性差,調試困難,性能并不出色,但使用它可以加快開發效率。本文將詳細介紹pug模板引擎
?
安裝
使用npm安裝pug
$ npm install pug
但運行pug命令時,提示pug命令未找到

這時,需要安裝pug命令行工具pug-cli
[注意]一定要全局安裝pug-cli,否則無法正常編譯
npm install pug-cli -g
再運行pug命令時,正常執行

?
命令行
在學習pug基礎語法之前,首先要了解pug的命令行的使用
【基礎編譯】
將如下內容輸入文件中,并命名為index.pug
htmlheadtitle aaabody
在命令行中敲入pug index.pug即可實現基礎編譯

在當前目錄下生成一個index.html,是index.pug編譯后的結果

【sublime兩列設置】
但是,這樣查看并不方便。下面將sublime設置為兩列放置,將index.pug和index.html分別放置在左右兩列,方便查看

【自動編譯】
使用pug -w功能可以實現自動編譯

更改index.pug文件并保存后,index.html文件會實時更新為最新的編譯的文件

【標準版HTML】
如上所示,默認地,pug編譯出的HTML文件是壓縮版的。如果要編譯標準版的HTML文件,需要設置-P參數
pug index.html -P
【路徑設置】
如果并不希望在當前目錄下輸入編譯后的HTML文件,而是有自定義目錄的需求,則需要設置-o參數
如下設置,index.html將輸入到a目錄下面,如果a目錄不存在,則會新建a目錄
pug index.pug -o a
【重命名】
默認地,編譯后的HTML與pug文件同名。如果需要重命名,則可以進行如下設置
通過如下設置,可以同時設置路徑和名稱
[注意]這里的路徑必須提前建立好,否則不會成功
pug <xx.pug> <xx/xx.html>
最終,test.html文件被保存到templates目錄下

【批量編譯】
假設,編譯href目錄下所有的pug文件

?
結構語法
下面介紹關于結構的基礎語法
標簽
【樹狀】
在默認情況下,在每行文本的開頭(或者緊跟白字符的部分)書寫這個 HTML 標簽的名稱。使用縮進來表示標簽間的嵌套關系,這樣可以構建一個 HTML 代碼的樹狀結構
ulli Item Ali Item Bli Item C

【內聯】
為了節省空間, Pug 嵌套標簽提供了一種內聯式語法
a: img

【自閉合】
Pug知道哪些元素是自閉合的,也可以通過在標簽后加上 /
來明確聲明此標簽是自閉合的
img input img/ input/

【DOCTYPE】
?HTML5的DOCTYPE書寫如下
doctype html

也可以自定義一個 doctype 字面值
doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"

?
內容
?Pug 提供了三種常用的方式來放置內容
【管道文本】
這是最簡單的向模板添加純文本的方法。只需要在每行前面加一個 |
字符,這個字符在類 Unix 系統下常用作“管道”功能,因此得名
| 純文本當然也可以包括 <strong>HTML</strong> 內容。
p| 但它必須單獨起一行。

【標簽內文本】
這實際上是最常見的情況,文本只需要和標簽名隔開一個空格即可
p 純文本當然也可以包括 <strong>HTML</strong> 內容。

【嵌入大段文本】
有時可能想要寫一個大段文本塊。比如嵌入腳本或者樣式。只需在標簽后面接一個 .
即可
[注意]不要有空格
script.if (usingPug)console.log('這是明智的選擇。')elseconsole.log('請用 Pug。')

?
屬性
標簽屬性和 HTML 語法非常相似,它們的值就是普通的 JavaScript 表達式。可以用逗號作為屬性分隔符,也可以不加逗號
a(href='baidu.com') 百度 = '\n' a(class='button' href='baidu.com') 百度 = '\n' a(class='button', href='baidu.com') 百度

【多行屬性】
如果有很多屬性,可以把它們分幾行寫
input(type='checkbox'name='agreement'checked )

【長屬性】
如果有一個很長的屬性,并且JavaScript運行時引擎支持ES2015模板字符串,可以使用它來寫屬性值
input(data-json=`{"非常": "長的","數據": true} `)

【特殊字符】
如果屬性名稱中含有某些奇怪的字符,可能會與 JavaScript 語法產生沖突的話,可以將它們使用 ""
或者 ''
括起來。還可以使用逗號來分割不同的屬性
div(class='div-class', (click)='play()') div(class='div-class' '(click)'='play()')

【轉義屬性】
默認情況下,所有的屬性都經過轉義(即把特殊字符轉換成轉義序列)來防止諸如跨站腳本攻擊之類的攻擊方式。如果要使用特殊符號,需要使用 !=
而不是 =
[注意]未經轉義的緩存代碼十分危險。必須正確處理和過濾用戶的輸入來避免跨站腳本攻擊
div(escaped="<code>") div(unescaped!="<code>")

【布爾值】
在Pug中,布爾值屬性是經過映射的,這樣布爾值(true
和false)
就能接受了。沒有指定值時,默認是true
input(type='checkbox' checked) = '\n' input(type='checkbox' checked=true) = '\n' input(type='checkbox' checked=false) = '\n' input(type='checkbox' checked=true.toString())

【行內樣式】
style
(樣式)屬性可以是一個字符串(就像其他普通的屬性一樣)還可以是一個對象
a(style={color: 'red', background: 'green'})

【類和ID】
類可以使用 .classname
語法來定義,ID 可以使用 #idname
語法來定義
考慮到使用 div
作為標簽名這種行為實在是太常見了,所以如果省略掉標簽名稱的話,它就是默認值
a.button .content ="\n" a#main-link #content

?
標簽嵌入
標簽支持一種標簽嵌入的寫法,形式如下
#[標簽名(標簽屬性) 標簽內容]
對于內聯標簽來說,這種寫法比較簡單
p.這是一個很長很長而且還很無聊的段落,還沒有結束,是的,非常非常地長。突然出現了一個 #[strong 充滿力量感的單詞],這確實讓人難以 #[em 忽視]。

【空格調整】
Pug 默認會去除一個標簽前后的所有空格,而標簽嵌入功能可以在需要嵌入的位置上處理前后空格
p| 如果我不用嵌入功能來書寫,一些標簽比如strong strong| 和em em| 可能會產生意外的結果。 p.如果用了嵌入,在 #[strong strong] 和 #[em em] 旁的空格就會讓我舒服多了。

??
注釋
【單行注釋】
單行注釋和 JavaScript 類似,但是必須獨立一行
// 一些內容 p foo p bar

【不輸出注釋】
只需要加上一個橫杠,就可以使用不輸出注釋
//- 這行不會出現在結果里 p foo p bar

【塊注釋】
body// 隨便寫多少字都沒關系。

【條件注釋】
Pug 沒有特殊的語法來表示條件注釋(conditional comments)。不過因為所有以 <
開頭的行都會被當作純文本,因此直接寫一個 HTML 風格的條件注釋也是沒問題的
<!--[if IE 8]> <html lang="en" class="lt-ie9"> <![endif]--> <!--[if gt IE 8]><!--> <html lang="en"> <!--<![endif]-->

?
邏輯語法
以下是關于模板邏輯的語法
JS代碼
【不輸出的代碼】
用 -
開始一段不直接進行輸出的代碼
- for (var x = 0; x < 3; x++)li item

【輸出的代碼】
用=
開始一段帶有輸出的代碼,它應該是可以被求值的一個JavaScript表達式。為安全起見,它將被HTML轉義
p= '這個代碼被 <轉義> 了!' p= '這個代碼被 <轉義> 了!'

【不轉義的輸出代碼】
用 !=
開始一段不轉義的,帶有輸出的代碼。這將不會做任何轉義,所以用于執行用戶的輸入將會不安全
p!= '這段文字 <strong>沒有</strong> 被轉義!' p!= '這段文字' + ' <strong>沒有</strong> 被轉義!'

?
變量
【內容變量】
使用=或#{}來進行變量的真實值替換
- var title = "On Dogs: Man's Best Friend"; - var author = "enlore"; - var theGreat = "<span>轉義!</span>";h1= title p #{author} 筆下源于真情的創作。 p 這是安全的:#{theGreat}

在?#{
?和?}
?里面的代碼也會被求值、轉義,并最終嵌入到模板的渲染輸出中
- var msg = "not my inside voice"; p This is #{msg.toUpperCase()}

Pug 足夠聰明來分辨到底哪里才是嵌入表達式的結束,所以不用擔心表達式中有?}
,也不需要額外的轉義
p 不要轉義 #{'}'}!

如果需要表示一個?#{
?文本,可以轉義它,也可以用嵌入功能來生成
p Escaping works with \#{interpolation} p Interpolation works with #{'#{interpolation}'} too!

使用!{}嵌入沒有轉義的文本進入模板中
- var riskyBusiness = "<em>我希望通過外籍教師 Peter 找一位英語筆友。</em>"; .quotep 李華:!{riskyBusiness}

[注意]如果直接使用用戶提供的數據,未進行轉義的內容可能會帶來安全風險
【屬性變量】
如果要在屬性當中使用變量的話,需要進行如下操作
- var url = 'pug-test.html'; a(href='/' + url) 鏈接 = '\n' - url = 'https://example.com/' a(href=url) 另一個鏈接

如果JavaScript運行時支持 ECMAScript 2015 模板字符串,還可以使用下列方式來簡化屬性值
- var btnType = 'info' - var btnSize = 'lg' button(type='button' class='btn btn-' + btnType + ' btn-' + btnSize) = '\n' button(type='button' class=`btn btn-${btnType} btn-${btnSize}`)

&attributes
?語法可以將一個對象轉化為一個元素的屬性列表
div#foo(data-bar="foo")&attributes({'data-foo': 'bar'}) - var attributes = {}; - attributes.class = 'baz'; div#foo(data-bar="foo")&attributes(attributes)

【變量來源】
變量來源有三種,分別是pug文件內部、命令行參數和外部JSON文件
1、pug文件內部

2、命令行參數
使用--obj參數,就可以跟隨一個對象形式的參數


3、外部JSON文件
使用-O,跟隨一個JSON文件的路徑即可


這三種方式,pug文件內部的變量優先級最多,而外部JSON文件和命令行傳參優先級相同
如下所示,外部JSON文件和命令行傳參兩種方式都存在,由于--obj寫在-w后面,最終以命令行傳參為準

?
條件
Pug 的條件判斷的一般形式的括號是可選的,所以可以省略掉開頭的 -
,效果完全相同。類似一個常規的 JavaScript 語法形式
【if else】
- var user = { description: 'foo bar baz' } - var authorised = false #userif user.descriptionh2.green 描述p.description= user.descriptionelse if authorisedh2.blue 描述p.description.用戶沒有添加描述。不寫點什么嗎……elseh2.red 描述p.description 用戶沒有描述

Pug 同樣也提供了它的反義版本 unless
unless user.isAnonymousp 您已經以 #{user.name} 的身份登錄。

【switch】
case
是 JavaScript 的 switch
指令的縮寫,并且它接受如下的形式
- var friends = 10 case friendswhen 0p 您沒有朋友when 1p 您有一個朋友defaultp 您有 #{friends} 個朋友

在某些情況下,如果不想輸出任何東西的話,可以明確地加上一個原生的 break
語句
- var friends = 0 case friendswhen 0- breakwhen 1p 您的朋友很少defaultp 您有 #{friends} 個朋友

也可以使用塊展開的語法
- var friends = 1 case friendswhen 0: p 您沒有朋友when 1: p 您有一個朋友default: p 您有 #{friends} 個朋友

?
循環
Pug 目前支持兩種主要的迭代方式: each
和 while
【each】
這是 Pug 的首選迭代方式
uleach val in [1, 2, 3, 4, 5]li= val

可以在迭代時獲得索引值
uleach val, index in ['〇', '一', '二']li= index + ': ' + val

能夠迭代對象中的鍵值
uleach val, index in {1:'一',2:'二',3:'三'}li= index + ': ' + val

用于迭代的對象或數組僅僅是個 JavaScript 表達式,因此它可以是變量、函數調用的結果,又或者其他
- var values = []; uleach val in values.length ? values : ['沒有內容']li= val

還能添加一個 else
塊,這個語句塊將會在數組與對象沒有可供迭代的值時被執行
- var values = []; uleach val in valuesli= valelseli 沒有內容

[注意]也可以使用 for
作為 each
的別稱
【while】
也可以使用 while
來創建一個循環
- var n = 0; ulwhile n < 4li= n++

?
混入
混入是一種允許在 Pug 中重復使用一整個代碼塊的方法
//- 定義 mixin listulli fooli barli baz //- 使用 +list +list

混入可以被編譯成函數形式,并傳遞一些參數
mixin pet(name)li.pet= name ul+pet('貓')+pet('狗')+pet('豬')

混入也可以把一整個代碼塊像內容一樣傳遞進來
mixin article(title).article.article-wrapperh1= titleif blockblockelsep 沒有提供任何內容。+article('Hello world')+article('Hello world')p 這是我p 隨便寫的文章

混入也可以隱式地,從“標簽屬性”得到一個參數 attributes

也可以直接用 &attributes
方法來傳遞 attributes
參數
mixin link(href, name)a(class!=attributes.class href=href)= name+link('/foo', 'foo')(class="btn")

[注意]+link(class="btn")
?等價于?+link()(class="btn")
,因為 Pug 會判斷括號內的內容是屬性還是參數。但最好使用后面的寫法,明確地傳遞空的參數,確保第一對括號內始終都是參數列表
可以用剩余參數(rest arguments)語法來表示參數列表最后傳入若干個長度不定的參數
mixin list(id, ...items)ul(id=id)each item in itemsli= item+list('my-list', 1, 2, 3, 4)

?
文件包含
包含(include)功能允許把另外的文件內容插入進來
//- index.pug doctype html htmlinclude includes/head.pugbodyh1 我的網站p 歡迎來到我這簡陋得不能再簡陋的網站。include includes/foot.pug
//- includes/head.pug headtitle 我的網站script(src='/javascripts/jquery.js')script(src='/javascripts/app.js')
//- includes/foot.pug footer#footerp Copyright (c) foobar

被包含的如果不是 Pug 文件,那么就只會當作文本內容來引入
//- index.pug doctype html htmlheadstyleinclude style.cssbodyh1 我的網站p 歡迎來到我這簡陋得不能再簡陋的網站。scriptinclude script.js
/* style.css */ h1 {color: red; }
// script.js console.log('真了不起!');

?
文件繼承
【覆蓋】
Pug 支持使用 block
和 extends
關鍵字進行模板的繼承。一個稱之為“塊”(block)的代碼塊,可以被子模板覆蓋、替換。這個過程是遞歸的。
Pug 的塊可以提供一份默認內容,當然這是可選的
//- layout.pug htmlhead
meta(charset="UTF-8")title 我的站點 - #{title}block scriptsscript(src='/jquery.js')bodyblock contentblock foot#footerp 一些頁腳的內容
現在來擴展這個布局:只需要簡單地創建一個新文件,并如下所示用一句 extends
來指出這個被繼承的模板的路徑。現在可以定義若干個新的塊來覆蓋父模板里對應的“父塊”。值得注意的是,因為這里的 foot
塊 沒有 被重定義,所以會依然輸出“一些頁腳的內容”
//- pet.pug p= petName
//- page-a.pug extends layout.pugblock scriptsscript(src='/jquery.js')script(src='/pets.js')block contenth1= title- var pets = ['貓', '狗']each petName in petsinclude pet.pug

同樣,也可以覆蓋一個塊并在其中提供一些新的塊。如下所示,content
塊現在暴露出兩個新的塊 sidebar
和 primary
用來被擴展。當然,它的子模板也可以把整個 content
給覆蓋掉
//- sub-layout.pug extends layout.pugblock content.sidebarblock sidebarp 什么都沒有.primaryblock primaryp 什么都沒有
//- page-b.pug extends sub-layout.pugblock content.sidebarblock sidebarp 什么都沒有.primaryblock primaryp 什么都沒有

【擴展】
Pug 允許去替換(默認的行為)、prepend
(向頭部添加內容),或者 append
(向尾部添加內容)一個塊。 假設有一份默認的腳本要放在 head
塊中,而且希望將它應用到 每一個頁面,可以進行如下操作
//- layout.pug htmlheadblock headscript(src='/vendor/jquery.js')script(src='/vendor/caustic.js')bodyblock content
現在假設有一個頁面,那是一個 JavaScript 編寫的游戲。希望把一些游戲相關的腳本也像默認的那些腳本一樣放進去,那么只要簡單地 append
這個塊:
//- page.pug extends layout.pugblock prepend headscript(src='/vendor/three.js')block append headscript(src='/game.js')
當使用 block append
或者 block prepend
時,block
關鍵字是可省略的:
//- page.pug extends layout.pugprepend headscript(src='/vendor/three.js')append headscript(src='/game.js')

?
簡易模板
//- index.pug doctype html htmlheadmeta(charset="UTF-8")title= documentTitleeach val in srcStyleslink(href= baseStyle +'/' + val)bodyheader.hdnav.hd-navbar.m-navbar.m-navbar_primary.hd-navbar-tel 聯系方式: #{tel}ul.hd-navbar-naveach val in mainNavItemli.Hnn-item.m-btn.m-btn_infoa(href="#")= valsection.mainh1.main-title 我的文檔p.main-content.這是一個很長很長而且還很無聊的段落,還沒有結束,是的,非常非常地長。突然出現了一個 #[strong 充滿力量感的單詞],這確實讓人難以 #[em 忽視]。footer.ftp Copyright (c) 小火柴的藍色理想each val in srcScriptsscript(src=baseScript + '/' + val)
//- data.json {"documentTitle":"測試文檔","tel":"400-888-8888","mainNavItem":['登錄','注冊','關于','幫助'],"baseStyle":'style',"srcStyles":['bootstrap.css','main.css'],"baseScript":'/js',"srcScripts":['jquery.js','app.js'] }


?