[1]安裝 [2]命令行 [3]標簽 [4]內容 [5]屬性 [6]註釋 [7]代碼 [8]條件 [9]迴圈 [10]混入 [11]包含 [12]繼承 [13]簡易模板 ...
前面的話
為什麼要引入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
html
head
title aaa
body
在命令行中敲入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 代碼的樹狀結構
ul
li Item A
li Item B
li 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('這是明智的選擇。') else console.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>";
.quote
p 李華:!{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 #user if user.description h2.green 描述 p.description= user.description else if authorised h2.blue 描述 p.description. 用戶沒有添加描述。 不寫點什麼嗎…… else h2.red 描述 p.description 用戶沒有描述
Pug 同樣也提供了它的反義版本 unless
unless user.isAnonymous
p 您已經以 #{user.name} 的身份登錄。
【switch】
case
是 JavaScript 的 switch
指令的縮寫,並且它接受如下的形式
- var friends = 10 case friends when 0 p 您沒有朋友 when 1 p 您有一個朋友 default p 您有 #{friends} 個朋友
在某些情況下,如果不想輸出任何東西的話,可以明確地加上一個原生的 break
語句
- var friends = 0 case friends when 0 - break when 1 p 您的朋友很少 default p 您有 #{friends} 個朋友
也可以使用塊展開的語法
- var friends = 1 case friends when 0: p 您沒有朋友 when 1: p 您有一個朋友 default: p 您有 #{friends} 個朋友
迴圈
Pug 目前支持兩種主要的迭代方式: each
和 while
【each】
這是 Pug 的首選迭代方式
ul each val in [1, 2, 3, 4, 5] li= val
可以在迭代時獲得索引值
ul each val, index in ['〇', '一', '二'] li= index + ': ' + val
能夠迭代對象中的鍵值
ul each val, index in {1:'一',2:'二',3:'三'} li= index + ': ' + val
用於迭代的對象或數組僅僅是個 JavaScript 表達式,因此它可以是變數、函數調用的結果,又或者其他
- var values = []; ul each val in values.length ? values : ['沒有內容'] li= val
還能添加一個 else
塊,這個語句塊將會在數組與對象沒有可供迭代的值時被執行
- var values = []; ul each val in values li= val else li 沒有內容
[註意]也可以使用 for
作為 each
的別稱
【while】
也可以使用 while
來創建一個迴圈
- var n = 0; ul while n < 4 li= n++
混入
混入是一種允許在 Pug 中重覆使用一整個代碼塊的方法
//- 定義 mixin list ul li foo li bar li baz //- 使用 +list +list
混入可以被編譯成函數形式,並傳遞一些參數
mixin pet(name) li.pet= name ul +pet('貓') +pet('狗') +pet('豬')
混入也可以把一整個代碼塊像內容一樣傳遞進來
mixin article(title) .article .article-wrapper h1= title if block block else p 沒有提供任何內容。 +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 items li= item +list('my-list', 1, 2, 3, 4)
文件包含
包含(include)功能允許把另外的文件內容插入進來
//- index.pug doctype html html include includes/head.pug body h1 我的網站 p 歡迎來到我這簡陋得不能再簡陋的網站。 include includes/foot.pug
//- includes/head.pug head title 我的網站 script(src='/javascripts/jquery.js') script(src='/javascripts/app.js')
//- includes/foot.pug footer#footer p Copyright (c) foobar
被包含的如果不是 Pug 文件,那麼就只會當作文本內容來引入
//- index.pug doctype html html head style include style.css body h1 我的網站 p 歡迎來到我這簡陋得不能再簡陋的網站。 script include script.js
/* style.css */ h1 { color: red; }
// script.js console.log('真了不起!');
文件繼承
【覆蓋】
Pug 支持使用 block
和 extends
關鍵字進行模板的繼承。一個稱之為“塊”(block)的代碼塊,可以被子模板覆蓋、替換。這個過程是遞歸的。
Pug 的塊可以提供一份預設內容,當然這是可選的
//- layout.pug html head
meta(charset="UTF-8") title 我的站點 - #{title} block scripts script(src='/jquery.js') body block content block foot #footer p 一些頁腳的內容
現在來擴展這個佈局:只需要簡單地創建一個新文件,並如下所示用一句 extends
來指出這個被繼承的模板的路徑。現在可以定義若幹個新的塊來覆蓋父模板里對應的“父塊”。值得註意的是,因為這裡的 foot
塊 沒有 被重定義,所以會依然輸出“一些頁腳的內容”
//- pet.pug p= petName
//- page-a.pug extends layout.pug block scripts script(src='/jquery.js') script(src='/pets.js') block content h1= title - var pets = ['貓', '狗'] each petName in pets include pet.pug
同樣,也可以覆蓋一個塊併在其中提供一些新的塊。如下所示,content
塊現在暴露出兩個新的塊 sidebar
和 primary
用來被擴展。當然,它的子模板也可以把整個 content
給覆蓋掉
//- sub-layout.pug extends layout.pug block content .sidebar block sidebar p 什麼都沒有 .primary block primary p 什麼都沒有
//- page-b.pug extends sub-layout.pug block content .sidebar block sidebar p 什麼都沒有 .primary block primary p 什麼都沒有
【擴展】
Pug 允許去替換(預設的行為)、prepend
(向頭部添加內容),或者 append
(向尾部添加內容)一個塊。 假設有一份預設的腳本要放在 head
塊中,而且希望將它應用到 每一個頁面,可以進行如下操作
//- layout.pug html head block head script(src='/vendor/jquery.js') script(src='/vendor/caustic.js') body block content
現在假設有一個頁面,那是一個 JavaScript 編寫的游戲。希望把一些游戲相關的腳本也像預設的那些腳本一樣放進去,那麼只要簡單地 append
這個塊:
//- page.pug extends layout.pug block prepend head script(src='/vendor/three.js') block append head script(src='/game.js')
當使用 block append
或者 block prepend
時,block
關鍵字是可省略的:
//- page.pug extends layout.pug prepend head script(src='/vendor/three.js') append head script(src='/game.js')
簡易模板
//- index.pug doctype html html head meta(charset="UTF-8") title= documentTitle each val in srcStyles link(href= baseStyle +'/' + val) body header.hd nav.hd-navbar.m-navbar.m-navbar_primary .hd-navbar-tel 聯繫方式: #{tel} ul.hd-navbar-nav each val in mainNavItem li.Hnn-item.m-btn.m-btn_info a(href="#")= val section.main h1.main-title 我的文檔 p.main-content. 這是一個很長很長而且還很無聊的段落,還沒有結束,是的,非常非常地長。 突然出現了一個 #[strong 充滿力量感的單詞],這確實讓人難以 #[em 忽視]。 footer.ft p Copyright (c) 小火柴的藍色理想 each val in srcScripts script(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'] }