Pug 模板引擎:学习与使用

在前端开发中,模板引擎的使用可以极大地提高代码的可读性和可维护性。Pug(也称为 Jade)是一个流行的 Node.js 模板引擎,它使用简洁的语法来创建 HTML 结构。由于在vue3文档中看到了Pug的影子,便决定学习体验一番。

官网:https://pugjs.org/zh-cn/api/getting-started.html

安装与配置

首先,确保你的项目中已经安装了 Node.jsnpm(Node 包管理器)。然后,可以通过 npm 来安装 Pug。在命令行中执行以下命令:

1
2
3
4
5
npm install pug
# 全局安装
npm install pug-cli -g
# 查看版本
pug --version

基础语法

Pug 的语法非常简洁,它使用缩进来表示 HTML 元素的层级关系,而不是使用传统的尖括号和结束标签。以下是一些基础语法的示例:

文档类型与 HTML 元素

1
2
3
4
5
6
doctype html
html
head
title My Pug Page
body
h1 Hello, Pug!

编译执行

1
2
3
4
5
pug index.pug
# 格式化的输出
pug index.pug -P
# 实时监控
pug -w . -o ./ -P

vscode 安装插件Pug beautify

缩进

pug对空格敏感,有点类似python对制表符tab敏感。pug使用空格作为缩进符,当然用soft tab也可行。同一级标签需保证左对齐。

1
2
3
div
p Hello, world!
p Hello, pug.

渲染结果如下:

1
2
3
4
<div>
<p>Hellow, world!</p>
<p>Hello, pug.</p>
</div>

注释

pug使用//-//对代码进行注释,前者注释内容不出现在渲染后的html文件中,后者反之。

1
2
//- html中不包含此行
// html中会包含此行

属性和ID

pug将标签属性存放于括号()内,多个属性之间以逗号或空格分隔。

此外,对于标签的idclasspug使用#紧跟标签id,使用.紧跟标签class,可以同时设置多个class

1
2
h1#title Test title
img#name.class1.class2(src="/test.png" alt="test")

会被编译为

1
2
<h1 id="title">Test title</h1>
<img class="class1 class2" id="name" src="/test.png" alt="test"/>

包含

为了方便代码复用,pug提供了include包含功能,以下代码会将_partial目录下的head.pug文件内容包含到当前调用的位置。有点C/C++中内联函数的意思。

1
2
3
doctype html
html(lang='en')
include _partial/head.pug

继承

下面是一个简单的base模板,通过block定义了页面头部head和内容body。块block有点类似C/C++的抽象函数,需要在继承者中完成定义,填充具体内容。

1
2
3
4
5
6
//- base.pug
html
head
block title
body
block content

变量与插值

pug中通过- var name = value的形式定义变量

1
2
3
4
5
6
- var intData = 100
- var boolData = false
- var stringData = 'Test'
p.int= intData
p.bool= boolData
p.stringData= stringData

需注意的是,在引用变量时,需要在引用位置加上=号,否则会默认将变量名当成普通字符串使用。

如果想要将变量与其它字符串常量或是变量连接在一起,就不能用等号了,而是应该用#{},该符号会对大括号内的变量进行求值和转义,最终得到渲染输出的内容。

1
2
3
4
- var girl = 'Lily'
- var boy = 'Jack'
p #{girl} is so beautiful!
p And #{boy} is handsome.

条件语句

pug的条件语句与其它语言类似,均是如下这般:

1
2
3
4
5
6
7
8
- var A = {value: 'Test'}
- var B = true
if A.value
p= A.value
else if B
p= B
else
p nothing

循环

pug中使用eachwhile实现循环迭代,each可以返回当前所在项的索引值,默认从0开始计数。

1
2
3
4
5
6
7
8
9
10
//- each
ol
each item in ['Sun', 'Mon', 'Tus', 'Wen', 'Thu', 'Fri', 'Sat']
li= item

//- get index of each
- var week = ['Sun', 'Mon', 'Tus', 'Wen', 'Thu', 'Fri', 'Sat']
ol
each item, index in week
li= index + ':' + item

混合

Pug 允许你创建可重用的代码块,称为混合(mixin),以及包含其他模板文件。

1
2
3
4
5
6
mixin list
ul
li Item 1
li Item 2

+list()

hexo 相关

hexo主题中使用pug时,可以通过使用hexo提供的全局变量configtheme来分别调用博客根目录下_config.yml文件中的参数以及主题根目录下_config.yml文件中的参数。

1
2
3
4
5
//- blog config
p= config.description

//- theme config
p= theme.title

当然,pug中可以直接使用hexo提供的其它全局变量及辅助函数,使用方法详见hexo文档

在 vue3 的 template 中使用 Pug

尽管 Pug 有自己的语法,可以定义变量,可以有 if 和 case-when 条件分支和 each-in 遍历,有自己的插值方式 #{} , 但如果在 template 里使用 Pug 语言,则不建议使用这些语法,而应该结合 Vue 的指令(如 v-if , v-for )和 mushache 插值方式。主要有以下原因:

一致性和可预测性:Vue 提供了自己的模板语法,该语法在 Vue 社区中广泛接受和使用。使用 Vue 的指令和插值方式可以确保代码的一致性和可预测性,这有助于其他 Vue 开发者更容易地理解和维护你的代码。

集成和兼容性:Vue 的指令和插值方式与 Vue 的响应式系统紧密集成。使用 Vue 的语法可以确保数据的变化能够正确地触发视图的更新。而使用 Pug 的原生语法可能会导致一些意料之外的行为或兼容性问题。

工具链支持:Vue 的模板语法在 Vue 的官方工具链(如 Vue CLI、Vite 等)中得到了很好的支持。这些工具可以帮助你进行代码拆分、热重载、静态分析等操作。相比之下,使用 Pug 可能会增加配置的复杂性,并可能减少与 Vue 工具链的集成度。

社区支持:Vue 的模板语法拥有庞大的社区支持,这意味着你可以更容易地找到相关的教程、文档和社区讨论。而 Pug 的社区虽然也很活跃,但可能与 Vue 社区不完全重合。

可读性和维护性:虽然 Pug 提供了一种简洁的语法来编写模板,但对于不熟悉 Pug 的开发者来说,它可能会增加阅读和维护的难度。使用 Vue 的模板语法可以使代码更加直观和易于理解。

安装对应插件

1
npm i pug pug-plain-loader

插值,属性和指令

  • mushache插值
1
2
3
4
5
6
7
8
<script setup lang="ts">
let title = "hello world",
</script>
<template lang="pug">
div
h1 {{ title }}
p This is a Pug template in Vue 3!
</template>

这段代码中使用 mushachetitle 进行插值

  • 属性 每个元素的属性都应该写在其后面的圆括号中,除了 idclass 可以简写。(所有的 指定都是属性,都应写在括号中)
1
2
3
4
5
<script setup lang="ts">
.box(id="box")
header(class="header")
p(class="title") {{ title }}
</template>
  • v-if条件分支
1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
import { ref } from "vue"

const isAdd = ref(true)

</script>
<template lang="pug">
button(v-if="isAdd") 新增
button(v-else) 编辑
</template>
  • v-for 遍历
1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">
import { ref } from "vue"

const boxText = ref([ 1,2,3,4,5])

</script>
<template lang="pug">
ul
li(v-for="(item, i) in boxText")
p {{ item }}
</template>
  • v-bindv-on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script setup lang="ts">
import { ref } from "vue"

const isAdd = ref(true)

const boxText = ref(['x','xx','xxx','xxxx','xxxx'])

const add = () => {}

const edit = () => {}

</script>
<template lang="pug">
ul
li(v-for="(item, i) in boxText")
p(:class="['p' + index]") {{ item }}

button(v-if="isAdd" @click="add") 新增
button(v-else @click="edit") 编辑
</template>
  • v-model 双向绑定
1
2
3
4
5
6
7
8
<script setup lang="ts">
import { ref } from 'vue'
const username = ref('');
</script>

<template lang="pug">
input(v-model="username" placeholder="请输入用户名")
</template>

插槽

  • 创建子组件(ChildComponent.vue)

在子组件中,可以定义默认插槽或具名插槽。例如:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- ChildComponent.vue -->

<script setup lang="ts">
// 定义组件的逻辑部分...
</script>

<template lang="pug">
div
h2 Child Component
slot() // 默认插槽
slot(name="namedSlot") // 具名插槽
</template>
  • 在父组件中使用子组件的插槽 (ParentComponent.vue)

在父组件中,可以使用 <template> 标签和 v-slot 指令来定义插槽内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- ParentComponent.vue -->

<script setup lang="ts">
import ChildComponent from './ChildComponent.vue';
</script>

<template lang="pug">
div
h1 Parent Component
ChildComponent
template(v-slot) // 默认插槽内容
p This is the default slot content.
template(v-slot:namedSlot) // 具名插槽内容
p This is the named slot content.
</template>

在上面的例子中,父组件 ParentComponent 使用了 ChildComponent,并通过

使用作用域插槽
对于作用域插槽,你可以在子组件的 slot 标签上绑定属性,然后在父组件的插槽模板中通过 v-slot 的值来接收这些属性。

子组件中定义作用域插槽:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- ChildComponent.vue -->

<script setup lang="ts">
import { ref } from 'vue';

const someItem = ref({ name: 'Example', value: 123 });
</script>

<template lang="pug">
div
slot(name="scopedSlot" :item="someItem") // 传递数据的作用域插槽
</template>

父组件中使用作用域插槽:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- ParentComponent.vue -->

<script setup lang="ts">
import ChildComponent from './ChildComponent.vue';
</script>

<template lang="pug">
div
h1 Parent Component
ChildComponent
template(v-slot:scopedSlot="{ item }") // 接收作用域插槽的数据
p {{ item.name }}: {{ item.value }}
</template>

在这个作用域插槽的例子中,子组件通过 slot 标签的 :item=”someItem” 传递了一个响应式对象 someItem 到作用域插槽。父组件通过v-slot:scopedSlot="{ item }" 来接收这个对象,并在模板中使用它。

总结

在 Vue 3 中使用 Pug(也称为 Jade)作为模板引擎,具有一些明显的优点和潜在的缺点。

  1. 优点:
    简洁的语法:Pug 的语法非常简洁,通过缩进和换行来表示元素之间的层级关系,减少了模板中的冗余代码。这使得代码更加清晰易读,也更容易编写和维护。
    提高开发效率:由于 Pug 的语法简洁,开发者可以更快地编写模板,提高开发效率。同时,Pug 还支持混合(mixin)功能,允许开发者定义可重用的代码块,进一步减少重复代码。
    强大的功能:Pug 提供了丰富的功能,如条件语句、循环、过滤器等,使得开发者能够在模板中实现复杂的逻辑。这些功能使得 Pug 在处理复杂页面布局和交互时具有很大的灵活性。
    与 Vue 3 的良好集成:Vue 3 支持使用不同的模板引擎,包括 Pug。Pug 可以与 Vue 3 的响应式系统、组件系统等无缝集成,使得开发者能够充分利用 Vue 3 的功能和特性。
  2. 缺点:
    学习成本:对于没有使用过 Pug 的开发者来说,需要花费一定的时间来学习其语法和特性。虽然 Pug 的语法相对简洁,但与 HTML 相比仍有一定的差异,这可能会增加学习成本。
    社区支持:虽然 Pug 是一个流行的模板引擎,但相对于 HTML 来说,其社区规模可能较小。这意味着在遇到问题时,可能难以找到足够的资源和支持。
    浏览器兼容性:由于 Pug 在编译时会转换为 HTML,因此在浏览器兼容性方面通常不会有问题。但是,如果开发者在 Pug 中使用了某些非标准的特性或语法,可能会导致在某些浏览器中出现问题。
    工具链集成:虽然 Vue 3 支持 Pug,但某些 Vue 生态中的工具链(如 Vue CLI 插件、构建工具等)可能不完全支持 Pug 或需要额外的配置。这可能会增加项目的复杂性和配置成本。
    因此,在选择是否使用 Pug 时,需要根据项目的需求和团队的实际情况进行权衡。