Pug(发音 /pʌɡ/),前身为 Jade,是一个高性能的 Node.js 模板引擎。它以其简洁、富有表现力的语法而闻名,旨在让 HTML 编写变得更加高效和愉快。Pug 摒弃了传统 HTML 的尖括号和闭合标签,转而使用缩进和基于文本的语法,这使得模板文件更小、更易读、也更不易出错。

核心思想:Pug 通过简洁的缩进语法替代冗长的 HTML 标签,提供强大的动态数据渲染、代码重用和条件逻辑功能。


一、Pug 简介

1.1 什么是模板引擎?

模板引擎是一种将数据填充到预定义模板中以生成最终输出(通常是 HTML 字符串)的工具。它将页面的结构(模板)与数据分离,使得前端开发更加模块化和可维护。

1.2 Pug 的特点

  • 独特语法:使用缩进表示嵌套关系,无需关闭标签。
  • 简洁明了:代码量显著少于对应的 HTML。
  • 强大功能:支持变量、循环、条件判断、Mixin(类似于函数或组件)、包含(文件复用)、布局继承等高级特性。
  • 编译到 HTML:Pug 模板最终会被编译成标准的 HTML。
  • Node.js 支持:作为 Node.js 的模板引擎,Pug 完美集成于 Express 等 Web 框架。
  • 社区活跃:拥有良好的社区支持和文档。

1.3 适用场景

  • Node.js 后端渲染 HTML 页面。
  • 静态站点生成。
  • 作为 HTML 预处理器,提高 HTML 编写效率。

二、Pug (Jade) 的安装与使用

Pug 通常作为 Node.js 项目的依赖项安装。

2.1 安装

使用 npm 或 yarn 安装:

1
2
3
npm install pug
# 或
yarn add pug

2.2 基本使用示例 (Node.js)

app.js (或 server.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const express = require('express');
const path = require('path');
const app = express();
const port = 3000;

// 设置 Pug 为模板引擎
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.get('/', (req, res) => {
res.render('index', {
title: 'Pug 示例',
message: '欢迎使用 Pug 模板引擎!',
user: { name: 'Alice', age: 30, isAdmin: true },
items: ['Apple', 'Banana', 'Cherry']
});
});

app.listen(port, () => {
console.log(`Pug App running at http://localhost:${port}`);
});

views/index.pug:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
doctype html
html(lang="zh-CN")
head
title= title + ' - Pug Page'
style.
body { font-family: sans-serif; }
ul { list-style: circle; }
.admin { color: red; font-weight: bold; }
body
h1= message
p Hello, #[strong #{user.name}]!
if user.isAdmin
p.admin 你是一名管理员。
else
p 你是一名普通用户。

h2 物品列表:
ul
each item in items
li= item

// Pug 注释,不会出现在 HTML 输出中
//- 这是另一个 Pug 注释

// HTML 注释,会出现在 HTML 输出中
// 这是普通的 HTML 注释

运行 node app.js 并在浏览器中访问 http://localhost:3000,你将看到一个渲染好的 HTML 页面。

三、Pug 核心语法详解

3.1 标签与缩进

Pug 最核心的特性是其基于缩进的语法。标签名后跟内容,子标签通过缩进表示嵌套。

1
2
3
div
p Hello, Pug!
img(src="/logo.png", alt="Logo")

编译为:

1
2
3
4
<div>
<p>Hello, Pug!</p>
<img src="/logo.png" alt="Logo">
</div>

3.2 属性

属性写在标签名后的圆括号内,多个属性用逗号或空格分隔。

1
a(href="/about", title="关于我们", target="_blank") 关于

编译为:

1
<a href="/about" title="关于我们" target="_blank">关于</a>

Class 和 ID 简写

Class 属性可以使用 . 前缀,ID 属性可以使用 # 前缀,可以直接跟在标签名之后。

1
2
div#container.wrapper.main
p#greeting.text-blue 你好!

编译为:

1
2
3
<div id="container" class="wrapper main">
<p id="greeting" class="text-blue">你好!</p>
</div>

布尔属性 (Boolean Attributes)

对于 checked, selected, disabled 等布尔属性,只需写属性名即可。

1
2
input(type="checkbox", checked)
input(type="text", disabled)

编译为:

1
2
<input type="checkbox" checked>
<input type="text" disabled>

如果属性值为 false, null, undefined0,则该布尔属性不会被渲染。

1
2
- var isChecked = false
input(type="checkbox", checked=isChecked)

编译为:

1
<input type="checkbox">

3.3 文本内容

行内文本

标签后直接跟文本内容。

1
p 这是行内文本。

块级文本

使用 . 运算符,或者直接在标签下方缩进文本块。

1
2
3
4
5
6
7
8
9
10
p.
这是一个很长的文本段落,
它会保持缩进和换行。

div
| 这是普通文本内容。
| 这是第二行。

span
!= '<B>这是一个非转义的 HTML 文本</B>'

编译为:

1
2
3
4
5
6
7
8
9
<p>这是一个很长的文本段落,
它会保持缩进和换行。</p>

<div>
这是普通文本内容。
这是第二行。
</div>

<span><B>这是一个非转义的 HTML 文本</B></span>
  • =:默认会转义 HTML 实体的文本输出。
  • !=不转义 HTML 实体的文本输出。在输出用户输入内容时使用要特别小心,防止 XSS 攻击。
  • .:用于多行纯文本块。

3.4 变量

使用 #{}= 来输出变量。

1
2
3
- var name = 'Pug User'
p Hello, #{name}!
h1= title // title 是从 res.render 传入的

编译为:

1
2
<p>Hello, Pug User!</p>
<h1>Pug 示例</h1>

3.5 条件判断 (Conditionals)

使用 if, else if, else 进行条件渲染。

1
2
3
4
5
6
if user.isAdmin
p 您是管理员。
else if user.isModerator
p 您是版主。
else
p 您是普通用户。

unless (反向判断)

1
2
unless user.isLoggedIn
p 请登录。

等同于 if !user.isLoggedIn

3.6 循环 (Iterations)

使用 each 循环遍历数组或对象。

1
2
3
4
5
6
7
ul
each fruit in items // items = ['Apple', 'Banana', 'Cherry']
li= fruit

ol
each val, key in {a: 1, b: 2, c: 3}
li #{key}: #{val}

编译为:

1
2
3
4
5
6
7
8
9
10
11
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul>

<ol>
<li>a: 1</li>
<li>b: 2</li>
<li>c: 3</li>
</ol>

3.7 Mixins (混合)

Mixins 允许你创建可重用的 Pug 块,类似于组件或函数,可以接受参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义 Mixin
mixin userProfile(user)
.user-card
h3= user.name
p 年龄: #{user.age}
if user.isAdmin
p.admin (管理员)
else
p (普通用户)

// 使用 Mixin
+userProfile({ name: 'Alice', age: 30, isAdmin: true })
+userProfile({ name: 'Bob', age: 25, isAdmin: false })

编译为:

1
2
3
4
5
6
7
8
9
10
<div class="user-card">
<h3>Alice</h3>
<p>年龄: 30</p>
<p class="admin">(管理员)</p>
</div>
<div class="user-card">
<h3>Bob</h3>
<p>年龄: 25</p>
<p>(普通用户)</p>
</div>

3.8 包含 (Includes)

使用 include 指令将其他 Pug 文件插入到当前模板中。

views/header.pug:

1
2
3
4
header
nav
a(href="/") 首页
a(href="/about") 关于

views/footer.pug:

1
2
footer
p 版权所有 &copy; 2024

views/index.pug:

1
2
3
4
5
6
7
8
9
doctype html
html
head
title 首页
body
include header.pug
main
h1 欢迎来到我的网站!
include footer.pug

3.9 布局继承 (Extends 和 Block)

布局继承是 Pug 中非常强大的特性,允许你定义一个基础布局模板,然后让其他模板继承并覆盖其中的特定块。这对于网站的统一结构非常有用。

views/layout.pug:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
doctype html
html
head
title
block title
| 默认标题
link(rel="stylesheet", href="/css/main.css")
block head_scripts
body
#header
h1 网站标题
block nav
nav
a(href="/") 首页
a(href="/contact") 联系我们

#content
block content
p 默认内容

#footer
p 版权所有
block foot_scripts
script(src="/js/analytics.js")

views/page.pug:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extends layout.pug // 继承 layout.pug

block title
| 关于我们

block nav
nav
a(href="/") 主页
a(href="/about") 关于 (当前)
a(href="/contact") 联系

block content
h2 关于公司
p 这是一个关于我们页面的具体内容。

block foot_scripts
script(src="/js/about.js")
#{super} // 保留父模板中的默认脚本

#{super} 允许你在覆盖一个 block 的同时,仍然包含父模板中该 block 的内容。

3.10 注释

  • //:Pug 单行注释,不会编译到 HTML。
  • //-:Pug 单行注释,不会编译到 HTML (与 // 相同)。
  • // Some HTML comment:Pug 将其识别为 HTML 注释,会编译到 HTML。
  • <!-- Some HTML comment -->:原生 HTML 注释,会编译到 HTML。

四、Pug 与 Express 框架集成

如在 app.js 示例所示,与 Express 集成非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const express = require('express');
const path = require('path');
const app = express();

// 告诉 Express 模板文件在哪里
app.set('views', path.join(__dirname, 'views'));
// 告诉 Express 使用哪个模板引擎
app.set('view engine', 'pug');

app.get('/', (req, res) => {
// res.render() 会查找 views 目录下的 index.pug 文件,
// 并传入第二个参数中的数据进行渲染
res.render('index', { title: '我的主页', message: '你好!' });
});

res.render() 方法会自动处理 Pug 模板的编译和 HTML 响应。

五、Pug 的优缺点

5.1 优点

  • HTML 编写效率高:大幅减少击键次数,无需手动闭合标签。
  • 代码简洁可读:缩进结构清晰,易于理解。
  • 强大的模板功能:变量、循环、条件判断、Mixin、Include、Extends 等一应俱全。
  • 避免 HTML 错误:由于 Pug 会编译成合法的 HTML,因此可以避免常见的 HTML 结构错误(如忘记关闭标签)。
  • 易于重构:模块化设计使得模板更易于维护和重构。

5.2 缺点

  • 学习曲线:对于习惯了传统 HTML 语法的开发者来说,Pug 的独特语法需要一定时间适应。
  • 调试相对困难:如果模板出现错误,错误信息可能指向编译后的 JavaScript 或 HTML 行数,而不是原始 Pug 文件的行数,这会增加调试难度(不过现代工具链有所改进)。
  • 语法严格:缩进是 Pug 语法的强制部分,错误的缩进会导致解析错误。
  • 非标准语法:Pug 文件本身不能直接在浏览器中运行,必须经过编译。

六、结语

Pug 是一个独特且功能强大的模板引擎,它通过创新的语法设计,极大地提升了 HTML 编写的效率和体验。虽然存在一定的学习成本,但一旦掌握,它能让前端模板的开发变得更加愉快和高效。在 Node.js 生态系统中,如果你追求简洁、高效和可维护的模板编写方式,Pug 绝对是一个值得尝试的优秀选择。