Node模板引擎學習(2)--Jade語法歸納

weixin_34148340發表於2018-03-22

Jade語法歸納

把 Jade 編譯為一個可供瀏覽器使用的單檔案,只需要簡單的執行:

make jade.js

如果你已經安裝了 uglifyjs (npm install uglify-js),你可以執行下面的命令它會生成所有的檔案。其實每一個正式版本里都幫你做了這事。

make jade.min.js

標籤

html

它會被轉換為 <html></html>

#foo
.bar

會輸出:

<div id="foo"></div><div class="bar"></div>

標籤文字

只需要簡單的把內容放在標籤之後:

p wahoo!

它會被渲染為 <p>wahoo!</p>.

大段的文字:

p
  | foo bar baz
  | rawr rawr
  | super cool
  | go jade go

渲染為 <p>foo bar baz rawr.....</p>

所有型別的文字展示都可以和資料結合起來,如果我們把 { name: 'tj', email: 'tj@vision-media.ca' }傳給編譯函式,下面是模板上的寫法:

#user #{name} &lt;#{email}&gt;

它會被渲染為 <div id="user">tj &lt;tj@vision-media.ca&gt;</div>

當就是要輸出 #{} 的時候怎麼辦? 轉義一下!

p \#{something}

它會輸出 <p>#{something}</p>

同樣可以使用非轉義的變數 !{html}, 下面的模板將直接輸出一個 <script> 標籤:

- var html = "<script></script>"
| !{html}

內聯標籤同樣可以使用文字塊來包含文字:

label
  | Username:
  input(name='user[name]')

或者直接使用標籤文字:

label Username:
  input(name='user[name]')

包含文字的標籤,比如 <script>, <style>, 和 <textarea> 不需要字首 | 字元, 比如:

html
  head
    title Example
    script
      if (foo) {
        bar();
      } else {
        baz();
      }

這裡還有一種選擇,可以使用 . 來開始一段文字塊,比如:

p.
  foo asdf
  asdf
   asdfasdfaf
   asdf
  asd.

會被渲染為:

<p>foo asdf
asdf
  asdfasdfaf
  asdf
asd
.
</p>

這和帶一個空格的 . 是不一樣的, 帶空格的會被 Jade 的解析器忽略,當作一個普通的文字:

p .

渲染為:

<p>.</p>

需要注意的是文字塊需要兩次轉義。比如想要輸出下面的文字:

</p>foo\bar</p>

使用:

p.
  foo\\bar

註釋

單行註釋和 JavaScript 裡是一樣的,通過 // 來開始,並且必須單獨一行:

// just some paragraphs
p foo
p bar

渲染為:

<!-- just some paragraphs -->
<p>foo</p>
<p>bar</p>

Jade 同樣支援不輸出的註釋,加一個短橫線就行了:

//- will not output within markup
p foo
p bar

渲染為:

<p>foo</p>
<p>bar</p>

塊註釋

塊註釋也是支援的:

body
  //
    #content
      h1 Example

渲染為:

<body>
  <!--
  <div id="content">
    <h1>Example</h1>
  </div>
  -->
</body>

Jade 同樣很好的支援了條件註釋:

body
  //if IE
    a(href='http://www.mozilla.com/en-US/firefox/') Get Firefox

渲染為:

<body>
  <!--[if IE]>
    <a href="http://www.mozilla.com/en-US/firefox/">Get Firefox</a>
  <![endif]-->
</body>

內聯

Jade 支援以自然的方式定義標籤巢狀:

ul
  li.first
    a(href='#') foo
  li
    a(href='#') bar
  li.last
    a(href='#') baz

塊展開

塊展開可以幫助你在一行內建立巢狀的標籤,下面的例子和上面的是一樣的:

ul
  li.first: a(href='#') foo
  li: a(href='#') bar
  li.last: a(href='#') baz

Case

case 表示式按下面這樣的形式寫:

html
  body
    friends = 10
    case friends
      when 0
        p you have no friends
      when 1
        p you have a friend
      default
        p you have #{friends} friends

塊展開在這裡也可以使用:

friends = 5
html
  body
    case friends
      when 0: p you have no friends
      when 1: p you have a friend
      default: p you have #{friends} friends

屬性

Jade 現在支援使用 () 作為屬性分隔符

a(href='/login', title='View login page') Login

當一個值是 undefined 或者 null 屬性 會被加上,
所以呢,它不會編譯出 'something="null"'.

div(something=null)

Boolean 屬性也是支援的:

input(type="checkbox", checked)

使用程式碼的 Boolean 屬性只有當屬性為 true 時才會輸出:

input(type="checkbox", checked=someValue)

多行同樣也是可用的:

input(type='checkbox',
  name='agreement',
  checked)

多行的時候可以不加逗號:

input(type='checkbox'
  name='agreement'
  checked)

加點空格,格式好看一點?同樣支援

input(
  type='checkbox'
  name='agreement'
  checked)

冒號也是支援的:

rss(xmlns:atom="atom")

假如我有一個 user 物件 { id: 12, name: 'tobi' }
我們希望建立一個指向 /user/12 的連結 href, 我們可以使用普通的 JavaScript 字串連線,如下:

a(href='/user/' + user.id)= user.name

或者我們使用 Jade 的修改方式, 這個我想很多使用 Ruby 或者 CoffeeScript 的人會看起來像普通的 JS..:

a(href='/user/#{user.id}')= user.name

class 屬性是一個特殊的屬性,你可以直接傳遞一個陣列,比如 bodyClasses = ['user', 'authenticated'] :

body(class=bodyClasses)

HTML

內聯的 HTML 是可以的,我們可以使用管道定義一段文字 :

html
  body
    | <h1>Title</h1>
    | <p>foo bar baz</p>

或者我們可以使用 . 來告訴 Jade 我們需要一段文字:

html
  body.
    <h1>Title</h1>
    <p>foo bar baz</p>

上面的兩個例子都會渲染成相同的結果:

<html><body><h1>Title</h1>
<p>foo bar baz</p>
</body></html>

這條規則適應於在 Jade 裡的任何文字:

html
  body
    h1 User <em>#{name}</em>

Doctypes

新增文件型別只需要簡單的使用 !!!, 或者 doctype 跟上下面的可選項:

!!!

會渲染出 transitional 文件型別, 或者:

!!! 5

!!! html

doctype html

Doctype 是大小寫不敏感的, 所以下面兩個是一樣的:

doctype Basic
doctype basic

當然也是可以直接傳遞一段文件型別的文字:

doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"

渲染後:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN">

會輸出 HTML5 文件型別. 下面的預設的文件型別,可以很簡單的擴充套件:

var doctypes = exports.doctypes = {
  '5': '<!DOCTYPE html>',
  'xml': '<?xml version="1.0" encoding="utf-8" ?>',
  'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
  'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
  '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
  'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
  'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
};

通過下面的程式碼可以很簡單的改變預設的文件型別:

    jade.doctypes.default = 'whatever you want';

過濾器

過濾器字首 :, 比如 :markdown 會把下面塊裡的文字交給專門的函式進行處理。檢視頂部 特性 裡有哪些可用的過濾器。

body
  :markdown
    Woah! jade _and_ markdown, very **cool**
    we can even link to [stuff](http://google.com)

渲染為:

<body><p>Woah! jade <em>and</em> markdown, very <strong>cool</strong> we can even link to <a href="http://google.com">stuff</a></p></body>

程式碼

Jade 目前支援三種型別的可執行程式碼。第一種是字首 -, 這是不會被輸出的:

- var foo = 'bar';

這可以用在條件語句或者迴圈中:

- for (var key in obj)
  p= obj[key]

由於 Jade 的快取技術,下面的程式碼也是可以的:

- if (foo)
  ul
    li yay
    li foo
    li worked
- else
  p oh no! didnt work

哈哈,甚至是很長的迴圈也是可以的:

- if (items.length)
  ul
    - items.forEach(function(item){
      li= item
    - })

所以你想要的!下一步我們要 轉義 輸出的程式碼,比如我們返回一個值,只要字首一個 =

- var foo = 'bar'
= foo
h1= foo

它會渲染為 bar<h1>bar</h1>. 為了安全起見,使用 = 輸出的程式碼預設是轉義的,如果想直接輸出不轉義的值可以使用 !=

p!= aVarContainingMoreHTML

Jade 同樣是設計師友好的,它可以使 JavaScript 更直接更富表現力。比如下面的賦值語句是相等的,同時表示式還是通常的 JavaScript:

- var foo = 'foo ' + 'bar'
foo = 'foo ' + 'bar'

Jade 會把 if, else if, else, until, while, unless 同別的優先對待, 但是你得記住它們還是普通的 JavaScript:

if foo == 'bar'
  ul
    li yay
    li foo
    li worked
else
  p oh no! didnt work  

迴圈

儘管已經支援 JavaScript 原生程式碼,Jade 還是支援了一些特殊的標籤,它們可以讓模板更加易於理解,其中之一就是 each, 這種形式:

each VAL[, KEY] in OBJ

一個遍歷陣列的例子 :

- var items = ["one", "two", "three"]
each item in items
  li= item

渲染為:

<li>one</li>
<li>two</li>
<li>three</li>

遍歷一個陣列同時帶上索引:

items = ["one", "two", "three"]
each item, i in items
  li #{item}: #{i}

渲染為:

<li>one: 0</li>
<li>two: 1</li>
<li>three: 2</li>

遍歷一個陣列的鍵值:

obj = { foo: 'bar' }
each val, key in obj
  li #{key}: #{val}

將會渲染為:<li>foo: bar</li>

Jade 在內部會把這些語句轉換成原生的 JavaScript 語句,就像使用 users.forEach(function(user){, 詞法作用域和巢狀會像在普通的 JavaScript 中一樣:

each user in users
  each role in user.roles
    li= role

如果你喜歡,也可以使用 for

for user in users
  for role in user.roles
    li= role

條件語句

Jade 條件語句和使用了(-) 字首的 JavaScript 語句是一致的,然後它允許你不使用圓括號,這樣會看上去對設計師更友好一點,同時要在心裡記住這個表示式渲染出的是 常規 JavaScript:

for user in users
  if user.role == 'admin'
    p #{user.name} is an admin
  else
    p= user.name

和下面的使用了常規 JavaScript 的程式碼是相等的:

for user in users
  - if (user.role == 'admin')
    p #{user.name} is an admin
  - else
    p= user.name

Jade 同時支援 unless, 這和 if (!(expr)) 是等價的:

for user in users
  unless user.isAnonymous
    p
      | Click to view
      a(href='/users/' + user.id)= user.name 

模板繼承

Jade 支援通過 blockextends 關鍵字來實現模板繼承。 一個塊就是一個 Jade 的 block ,它將在子模板中實現,同時是支援遞迴的。
Jade 塊如果沒有內容,Jade 會新增預設內容,下面的程式碼預設會輸出 block scripts, block content, 和 block foot.

html
  head
    h1 My Site - #{title}
    block scripts
      script(src='/jquery.js')
  body
    block content
    block foot
      #footer
        p some footer content

現在我們來繼承這個佈局,簡單建立一個新檔案,像下面那樣直接使用 extends,給定路徑(可以選擇帶 .jade 副檔名或者不帶). 你可以定義一個或者更多的塊來覆蓋父級塊內容, 注意到這裡的 foot沒有 定義,所以它還會輸出父級的 "some footer content"。

extends extend-layout

block scripts
  script(src='/jquery.js')
  script(src='/pets.js')

block content
  h1= title
  each pet in pets
    include pet

同樣可以在一個子塊裡新增塊,就像下面實現的塊 content 裡又定義了兩個可以被實現的塊 sidebarprimary,或者子模板直接實現 content

extends regular-layout

block content
  .sidebar
    block sidebar
      p nothing
  .primary
    block primary
      p nothing

前置、追加程式碼塊

Jade允許你 替換 (預設)、 前置追加 blocks. 比如,假設你希望在 所有 頁面的頭部都加上預設的指令碼,你可以這麼做:

html
  head
    block head
      script(src='/vendor/jquery.js')
      script(src='/vendor/caustic.js')
  body
    block content

現在假設你有一個Javascript遊戲的頁面,你希望在預設的指令碼之外新增一些遊戲相關的指令碼,你可以直接append上程式碼塊:

extends layout

block append head
  script(src='/vendor/three.js')
  script(src='/game.js')

使用 block appendblock prependblock 是可選的:

extends layout

append head
  script(src='/vendor/three.js')
  script(src='/game.js')

包含

Includes 允許你靜態包含一段 Jade, 或者別的存放在單個檔案中的東西比如 CSS, HTML 非常常見的例子是包含頭部和頁尾。 假設我們有一個下面目錄結構的資料夾:

./layout.jade
./includes/
  ./head.jade
  ./tail.jade

下面是 layout.jade 的內容:

html
  include includes/head  
  body
    h1 My Site
    p Welcome to my super amazing site.
    include includes/foot

這兩個包含 includes/headincludes/foot 都會讀取相對於給 layout.jade 引數filename 的路徑的檔案, 這是一個絕對路徑,不用擔心Express幫你搞定這些了。Include 會解析這些檔案,並且插入到已經生成的語法樹中,然後渲染為你期待的內容:

<html>
  <head>
    <title>My Site</title>
    <script src="/javascripts/jquery.js">
    </script><script src="/javascripts/app.js"></script>
  </head>
  <body>
    <h1>My Site</h1>
    <p>Welcome to my super lame site.</p>
    <div id="footer">
      <p>Copyright>(c) foobar</p>
    </div>
  </body>
</html>

前面已經提到,include 可以包含比如 HTML 或者 CSS 這樣的內容。給定一個副檔名後,Jade 不會把這個檔案當作一個 Jade 原始碼,並且會把它當作一個普通文字包含進來:

html
  head
    //- css and js have simple filters that wrap them in
        <style> and <script> tags, respectively
    include stylesheet.css
    include script.js
  body
    //- "markdown" files will use the "markdown" filter
        to convert Markdown to HTML
    include introduction.markdown
    //- html files have no filter and are included verbatim
    include content.html

Include 也可以接受塊內容,給定的塊將會附加到包含檔案 最後 的塊裡。 舉個例子,head.jade 包含下面的內容:

head
  script(src='/jquery.js')

我們可以像下面給include head新增內容, 這裡是新增兩個指令碼.

html
  include head
    script(src='/foo.js')
    script(src='/bar.js')
  body
    h1 test

在被包含的模板中,你也可以使用yield語句。yield語句允許你明確地標明include的程式碼塊的放置位置。比如,假設你希望把程式碼塊放在scripts之前,而不是附加在scripts之後:

head
  yield
  script(src='/jquery.js')
  script(src='/jquery.ui.js')

由於被包含的Jade會按字面解析併合併到AST中,詞法範圍的變數的效果和直接寫在同一個檔案中的相同。這就意味著include可以用作partial的替代,例如,假設我們有一個引用了user變數的user.jade`檔案:

h1= user.name
p= user.occupation

接著,當我們迭代users的時候,只需簡單地加上include user。因為在迴圈中user變數已經被定義了,被包含的模板可以訪問它。

users = [{ name: 'Tobi', occupation: 'Ferret' }]
each user in users
  .user
    include user

以上程式碼會生成:

<div class="user">
  <h1>Tobi</h1>
  <p>Ferret</p>
</div>

user.jade引用了user變數,如果我們希望使用一個不同的變數user,那麼我們可以直接定義一個新變數user = person,如下所示:

each person in users
  .user
    user = person
    include user

Mixins

Mixins 在編譯的模板裡會被 Jade 轉換為普通的 JavaScript 函式。 Mixins 可以帶引數,但不是必需的:

mixin list
  ul
    li foo
    li bar
    li baz

使用不帶引數的 mixin 看上去非常簡單,在一個塊外:

h2 Groceries
mixin list

Mixins 也可以帶一個或者多個引數,引數就是普通的 JavaScript 表示式,比如下面的例子:

mixin pets(pets)
  ul.pets
    - each pet in pets
      li= pet

mixin profile(user)
  .user
    h2= user.name
    mixin pets(user.pets)

會輸出像下面的 HTML:

<div class="user">
  <h2>tj</h2>
  <ul class="pets">
    <li>tobi</li>
    <li>loki</li>
    <li>jane</li>
    <li>manny</li>
  </ul>
</div>

產生輸出

假設我們有下面的 Jade 原始碼:

- var title = 'yay'
h1.title #{title}
p Just an example

compileDebug 選項不是 false, Jade 會編譯時會把函式里加上 __.lineno = n;, 這個引數會在編譯出錯時傳遞給 rethrow(), 而這個函式會在 Jade 初始輸出時給出一個有用的錯誤資訊。

function anonymous(locals) {
  var __ = { lineno: 1, input: "- var title = 'yay'\nh1.title #{title}\np Just an example", filename: "testing/test.js" };
  var rethrow = jade.rethrow;
  try {
    var attrs = jade.attrs, escape = jade.escape;
    var buf = [];
    with (locals || {}) {
      var interp;
      __.lineno = 1;
       var title = 'yay'
      __.lineno = 2;
      buf.push('<h1');
      buf.push(attrs({ "class": ('title') }));
      buf.push('>');
      buf.push('' + escape((interp = title) == null ? '' : interp) + '');
      buf.push('</h1>');
      __.lineno = 3;
      buf.push('<p>');
      buf.push('Just an example');
      buf.push('</p>');
    }
    return buf.join("");
  } catch (err) {
    rethrow(err, __.input, __.filename, __.lineno);
  }
}

compileDebug 引數是 false, 這個引數會被去掉,這樣對於輕量級的瀏覽器端模板是非常有用的。結合 Jade 的引數和當前原始碼庫裡的 ./runtime.js 檔案,你可以通過 toString() 來編譯模板而不需要在瀏覽器端執行整個 Jade 庫,這樣可以提高效能,也可以減少載入的 JavaScript 數量。

function anonymous(locals) {
  var attrs = jade.attrs, escape = jade.escape;
  var buf = [];
  with (locals || {}) {
    var interp;
    var title = 'yay'
    buf.push('<h1');
    buf.push(attrs({ "class": ('title') }));
    buf.push('>');
    buf.push('' + escape((interp = title) == null ? '' : interp) + '');
    buf.push('</h1>');
    buf.push('<p>');
    buf.push('Just an example');
    buf.push('</p>');
  }
  return buf.join("");
}

Makefile 的一個例子

通過執行 make, 下面的 Makefile 例子可以把 pages/*.jade 編譯為 pages/*.html

JADE = $(shell find pages/*.jade)
HTML = $(JADE:.jade=.html)

all: $(HTML)

%.html: %.jade
    jade < $< --path $< > $@

clean:
    rm -f $(HTML)

.PHONY: clean

這個可以和 watch(1) 命令起來產生像下面的行為:

$ watch make

命令列的 Jade

使用: jade [options] [dir|file ...]

選項:
  -h, --help         輸出幫助資訊
  -v, --version      輸出版本號
  -o, --out <dir>    輸出編譯後的 HTML 到 <dir>
  -O, --obj <str>    JavaScript 選項
  -p, --path <path>  在處理 stdio 時,查詢包含檔案時的查詢路徑
  -P, --pretty       格式化 HTML 輸出
  -c, --client       編譯瀏覽器端可用的 runtime.js
  -D, --no-debug     關閉編譯的除錯選項(函式會更小)
  -w, --watch        監視檔案改變自動重新整理編譯結果

Examples:
  # 編譯整個目錄
  $ jade templates

  # 生成 {foo,bar}.html
  $ jade {foo,bar}.jade

  # 在標準IO下使用jade 
  $ jade < my.jade > my.html

  # 在標準IO下使用jade, 同時指定用於查詢包含的檔案
  $ jade < my.jade -p my.jade > my.html

  # 在標準IO下使用jade 
  $ echo "h1 Jade!" | jade

  # foo, bar 目錄渲染到 /tmp
  $ jade foo bar --out /tmp 

注意: 從 v0.31.0-o 選項已經指向 --out, -O 相應做了交換

相關文章