模板引擎 Pug

Pug(原名 Jade)是一款深受开发者喜爱的模板引擎,它以简洁的缩进语法代替繁琐的 HTML 标签,让视图代码更加清晰易写。Pug 功能强大,支持模板继承、包含、混入等高级特性,并与 Express 无缝集成。本章将带你深入掌握 Pug 的用法。

1. 什么是 Pug?

Pug 是一个受 Haml 影响的模板引擎,使用缩进表示 HTML 标签的层级关系,摒弃了尖括号,极大地简化了 HTML 的编写。它最初名为 Jade,后因商标问题更名为 Pug。Pug 的特点包括:

  • 极简语法:无需闭合标签,通过缩进表示嵌套。
  • 变量插值:轻松嵌入动态数据。
  • 逻辑支持:条件、循环等 JavaScript 代码可以直接使用。
  • 模板继承:通过 extendsblock 实现布局复用。
  • 包含与混入:复用公共代码片段。

2. 安装与配置

在 Express 项目中使用 Pug 非常简单。首先安装 pug 包:

npm install pug

然后在主应用文件中设置模板引擎:

const express = require('express');
const path = require('path');
const app = express();

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

// 静态文件服务(可选)
app.use(express.static(path.join(__dirname, 'public')));

现在,你可以在 views 目录下创建 .pug 文件,并通过 res.render() 渲染它们。

3. Pug 基本语法

Pug 的核心思想是用缩进代替标签嵌套。下面我们从最简单的例子开始。

3.1 标签与文本

在 Pug 中,一行开头的文本默认被解析为 HTML 标签。标签后的空格后的文本即为标签内容。

h1 欢迎使用 Pug
p 这是一个段落。

生成 HTML:

<h1>欢迎使用 Pug</h1>
<p>这是一个段落。</p>

3.2 属性

属性放在标签后的括号内,格式为 属性=值,多个属性用逗号分隔。

a(href="https://example.com", target="_blank") 链接
img(src="/images/logo.png", alt="Logo")

生成:

<a href="https://example.com" target="_blank">链接</a>
<img src="/images/logo.png" alt="Logo">

3.3 类和 ID 简写

可以使用 CSS 选择器语法快速添加类和 ID。

div.container
  #main.content
    p.lead 主要内容

生成:

<div class="container">
  <div id="main" class="content">
    <p class="lead">主要内容</p>
  </div>
</div>

3.4 多行文本

如果文本内容较多,可以在标签后加一个点 .,然后缩进写多行文本。

p.
  这是第一行。
  这是第二行。
  这是第三行。

或者使用 | 管道符表示文本行。

p
  | 第一行
  | 第二行
  | 第三行

3.5 变量输出

使用 #{variable}= variable 输出转义后的变量值(防止 XSS)。

- var name = '张三'
p 你好,#{name}!
p= '你好,' + name

如果需要输出原始 HTML(不转义),使用 !{variable}!= variable

- var html = '<strong>加粗</strong>'
p!{html}
安全提示: 使用不转义输出时,请确保变量内容可信,否则可能导致 XSS 漏洞。

3.6 注释

Pug 支持两种注释:

  • // 单行注释:会输出到 HTML。
  • //- 不输出的注释:仅存在于 Pug 文件中。
// 这个注释会出现在 HTML 中
//- 这个注释不会出现在 HTML 中
p 段落

4. 逻辑控制

Pug 允许在模板中直接使用 JavaScript 代码,通过以 - 开头的行来执行逻辑。

4.1 条件判断

- var user = { name: '李四' }
if user
  p 欢迎回来,#{user.name}!
else
  p 请登录。

也可以使用 unless(相当于 if (!...))。

4.2 循环

使用 each 遍历数组或对象。

ul
  each item in ['苹果', '香蕉', '橙子']
    li= item

// 遍历对象
dl
  each value, key in {name: '张三', age: 30}
    dt= key
    dd= value

还可以使用 while 循环。

5. 模板继承(Layouts)

Pug 通过 extendsblock 实现模板继承,非常适合定义页面布局。

创建布局文件 views/layout.pug

doctype html
html
  head
    title #{title}
    link(rel="stylesheet", href="/css/style.css")
  body
    header
      h1 我的网站
      include partials/nav
    main
      block content
    footer
      p 版权所有 © 2025

创建导航部分 views/partials/nav.pug

nav
  a(href="/") 首页
  a(href="/about") 关于
  a(href="/contact") 联系

在具体页面中使用布局: views/index.pug

extends layout

block content
  h2 欢迎访问首页
  p 这是使用 Pug 布局生成的页面。

在路由中渲染:

app.get('/', (req, res) => {
  res.render('index', { title: '首页' });
});

6. 包含(Include)

include 用于将其他 Pug 文件内容嵌入到当前文件中。上例中已经演示了包含导航。包含的文件可以是 Pug、纯文本或 CSS/JS 等。

// 包含 Pug 文件
include partials/footer

// 包含纯文本文件
include:markdown-it README.md

// 包含 CSS 文件(作为内联样式)
style
  include style.css

7. 混入(Mixins)

混入允许定义可复用的代码块,类似函数。

// 定义混入
mixin list(items)
  ul
    each item in items
      li= item

// 使用混入
+list(['苹果', '香蕉', '橙子'])

混入还可以接受选项块:

mixin article(title)
  .article
    .article-header
      h2= title
      if block
        block
    .article-content
      p 这是文章内容。

+article('标题')
  p 这是自定义的头部描述。

8. 与 Express 集成

在 Express 中渲染 Pug 模板时,可以通过 res.render() 的第二个参数传递数据。

app.get('/profile', (req, res) => {
  res.render('profile', {
    user: { name: '王五', age: 28 },
    skills: ['JavaScript', 'Node.js', 'Express']
  });
});

profile.pug 中使用数据:

extends layout

block content
  h2 用户资料
  p 姓名:#{user.name}
  p 年龄:#{user.age}
  h3 技能:
  ul
    each skill in skills
      li= skill

9. 完整示例:博客系统视图

结合上述特性,构建一个简单的博客列表和详情页。

app.js(片段)

const posts = [
  { id: 1, title: '第一篇博客', content: '这是第一篇博客的内容。' },
  { id: 2, title: '第二篇博客', content: '这是第二篇博客的内容。' }
];

app.get('/', (req, res) => {
  res.render('index', { title: '博客首页', posts });
});

app.get('/post/:id', (req, res) => {
  const post = posts.find(p => p.id == req.params.id);
  if (post) {
    res.render('post', { title: post.title, post });
  } else {
    res.status(404).render('404', { title: '未找到' });
  }
});

views/layout.pug

doctype html
html
  head
    title= title
    link(rel="stylesheet", href="/css/style.css")
  body
    header
      h1: a(href="/") 我的博客
      nav
        a(href="/") 首页
        a(href="/about") 关于
    main
      block content
    footer
      p © 2025 我的博客

views/index.pug

extends layout

block content
  h2 最新文章
  if posts.length
    each post in posts
      article
        h3: a(href=`/post/${post.id}`)= post.title
        p= post.content.substring(0, 100) + '...'
  else
    p 暂无文章。

views/post.pug

extends layout

block content
  article
    h2= post.title
    p= post.content
    p: a(href="/") 返回首页

views/404.pug

extends layout

block content
  h2 404 - 页面未找到
  p 您访问的页面不存在。

10. 注意事项与最佳实践

  • 缩进一致性:Pug 依赖缩进表示层级,建议使用 2 个空格缩进,避免混用空格和 Tab。
  • 变量命名:避免与 Pug 关键字冲突。
  • 逻辑分离:不要在模板中写复杂业务逻辑,保持视图简洁。
  • 缓存:生产环境中,Pug 会自动缓存编译后的模板,提升性能。
  • 转义:默认输出是转义的,除非明确使用 !{}
总结: Pug 以其极简的语法和强大的功能,成为 Node.js 开发者喜爱的模板引擎之一。通过本章的学习,你应该能够熟练使用 Pug 编写视图,并利用继承、包含、混入等特性构建可维护的页面。结合 Express,你可以快速开发出优雅的 Web 应用。