Jade —— 源于 Node.js 的 HTML 模板引擎

Jade 是一个高性能的模板引擎,它深受 Haml 影响,它是用 JavaScript 实现的,而且能够供 Node 使用。javascript

jade

试玩

你能够在网上试玩 Jadephp

特性

  • 客户端支持
  • 代码高可读
  • 灵活的缩进
  • 块展开
  • Mixins
  • 静态包含
  • 属性改写
  • 安全,默认代码是转义的
  • 运行时和编译时上下文错误报告
  • 命令行下编译jade模板
  • HTML5 模式 (使用 !!! 5 文档类型)
  • 在内存中缓存(可选)
  • 合并动态和静态标签类
  • 能够经过 filters 修改树
  • 模板继承
  • 原生支持 Express JS
  • 经过 each 枚举对象、数组甚至是不能枚举的对象
  • 块注释
  • 没有前缀的标签
  • AST Filters
  • 过滤器
  • Emacs Mode
  • Vim Syntax
  • TextMate Bundle
  • Coda/SubEtha syntax Mode
  • Screencasts
  • html2jade 转换器

其它实现

jade有其余语言的实现,能够实现先后端渲染的统一:css

安装

npm install jade 

浏览器支持

把 Jade 编译为一个可供浏览器使用的单文件,只须要简单的执行:html

make jade.js 

若是你已经安装了 uglifyjs (npm install uglify-js),你能够执行下面的命令它会生成全部的文件。其实每个正式版本里都帮你作了这事。java

make jade.min.js 

默认状况下,为了方便调试Jade会把模板组织成带有形如 __.lineno = 3 的行号的形式。 在浏览器里使用的时候,你能够经过传递一个选项 { compileDebug: false } 来去掉这个。 下面的模板node

p Hello #{name} 

会被翻译成下面的函数:python

function anonymous(locals, attrs, escape, rethrow) { var buf = []; with (locals || {}) { var interp; buf.push('\n<p>Hello ' + escape((interp = name) == null ? '' : interp) + '\n</p>'); } return buf.join(""); } 

经过使用 Jade 的 ./runtime.js你能够在浏览器使用这些预编译的模板而不须要使用 Jade, 你只须要使用 runtime.js 里的工具函数, 它们会放在 jade.attrs, jade.escape 这些里。 把选项 { client: true } 传递给 jade.compile(), Jade 会把这些帮助函数的引用放在jade.attrs, jade.escape.jquery

function anonymous(locals, attrs, escape, rethrow) { var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow; var buf = []; with (locals || {}) { var interp; buf.push('\n<p>Hello ' + escape((interp = name) == null ? '' : interp) + '\n</p>'); } return buf.join(""); } 

API

var jade = require('jade'); // Compile a function var fn = jade.compile('string of jade', options); fn(locals); 

选项

  • self      使用 self 命名空间来持有本地变量. (默认为 false)
  • locals    本地变量对象
  • filename  异常发生时使用,includes 时必需
  • debug     输出 token 和翻译后的函数体
  • compiler  替换掉 jade 默认的编译器
  • compileDebug  false的时候调试的结构不会被输出
  • pretty    为输出加上了漂亮的空格缩进 (默认为 false)

标签

标签就是一个简单的单词:nginx

html 

它会被转换为 <html></html>git

标签也是能够有 id 的:

div#container 

它会被转换为 <div id="container"></div>

怎么加 class 呢?

div.user-details

转换为 <div class="user-details"></div>

多个 class 和 id? 也是能够搞定的:

div#foo.bar.baz 

转换为 <div id="foo" class="bar baz"></div>

不停的 div div div 很讨厌啊 , 能够这样:

#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 相应作了交换

相关文章
相关标签/搜索