在Node.js中使用Express Pug视图引擎进行模板设计
每个流行的模板引擎都有自己的定义占位符的理念。在这篇文章中,我们来看看Pug以及如何在NodeJS和Express中使用它。
在我们的HTML页面中呈现动态内容是一个常见的要求。模板引擎是支持这一功能的一个好方法。在这篇文章中,我们将学习如何使用Express Pug视图引擎在NodeJS中执行模板化。
如果你是Express的新手,可以看看这篇关于ExpressJS入门的文章。
1.什么是模板引擎?
模板引擎有助于使我们的Web应用程序在数据方面具有动态性。在模板引擎的核心,有一个HTML模板,上面有动态数据的占位符。
当一个请求被我们的应用程序处理时,模板引擎就开始行动。基本上,该引擎将占位符或片段替换为实际的HTML内容。换句话说,模板引擎执行即时生成的HTML,并将其发送到客户端。
一些最流行的模板引擎是:
- EJS
- Pug(也被称为Jade)
- Handlebars
每个引擎都有自己的定义占位符的理念。在这篇文章中,我们将看看Pug,以及我们如何在NodeJS和Express中使用它。
2.NodeJS Express Pug的安装
作为在我们的Express应用中使用Pug的第一步,我们需要安装必要的软件包。
要做到这一点,我们可以执行下面的命令:
$ npm install express pug body-parser
下面是我们的例子应用程序的package.json:
{
"name": "nodejs-express-pug-sample",
"version": "1.0.0",
"description": "NodeJS Express Pug Sample",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon app.js",
"start-server": "node app.js"
},
"author": "Saurabh Dashora",
"license": "ISC",
"devDependencies": {
"nodemon": "^1.18.3"
},
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.3",
"pug": "^3.0.2"
}
}
作为一个模板引擎,Pug使用最少的HTML。相反,它更倾向于一种自定义的模板语言。
现在让我们看看如何使用Pug建立一些模板。
3.创建NodeJS Express Pug模板
为了我们的演示应用程序,我们将创建一个简单的应用程序,它将显示一个产品的列表。此外,我们还将有一个页面来添加产品到我们的商店。
让我们首先创建产品列表页模板。我们将把模板放在我们的源代码中,放在一个特殊的views 文件夹中。文件名将是shop.pug 。
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title #{pageTitle}
link(rel="stylesheet", href="/css/main.css")
link(rel="stylesheet", href="/css/product.css")
body
header.main-header
nav.main-header__nav
ul.main-header__item-list
li.main-header__item
a.active(href="/") Shop
li.main-header__item
a(href="/admin/add-product") Add Product
main
if prods.length > 0
.grid
each product in prods
article.card.product-item
header.card__header
h1.product_title #{product.title}
.card__image
img(src="https://cdn.pixabay.com/photo/2015/11/19/21/10/glasses-1052010__340.jpg" alt="Product Image")
.card__content
h2.product__price $9.99
p2.product_description The Best Book Ever Written
.card__actions
button.btn Add To Cart
else
h1 No Products
正如你所看到的,Pug模板看起来几乎不像是我们熟悉的HTML语法。当然,我们仍然使用常见的HTML标签来描述页面。
在Pug模板中,缩进是非常重要的。因此,我们需要注意适当的缩进,以传达我们的HTML结构的层次性。
在Pug模板中,我们也可以指定CSS类为header.main-header 。这里,header 是HTML标签,main-header 是CSS类。
我们还可以在Pug模板中声明一个表达式,如if prods.length > 0 。这里,prods ,包含了我们商店中的产品列表。另外,我们可以用#{product.title} ,为我们的HTML中的动态数据定义一个占位符。在请求执行时,Pug模板引擎会用实际的数据替换占位符。
同样地,我们可以定义一个Pug模板来添加产品。
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Add Product
link(rel="stylesheet", href="/css/main.css")
link(rel="stylesheet", href="/css/forms.css")
link(rel="stylesheet", href="/css/product.css")
body
header.main-header
nav.main-header__nav
ul.main-header__item-list
li.main-header__item
a(href="/") Shop
li.main-header__item
a.active(href="/admin/add-product") Add Product
main
form.product-form(action="/admin/add-product", method="POST")
.form-control
label(for="title") Title
input(type="text", name="title", id="title")
button.btn(type="submit") Add Product
在这里,我们对标题和导航栏有一个类似的结构。然而,我们呈现的不是一个产品列表,而是一个带有一个产品标题输入字段的表单。
4.创建NodeJS Express路由
现在我们的模板已经准备好了,我们需要编写适当的逻辑来服务这些模板。
要做到这一点,我们将首先为我们的应用程序创建app.js 文件。基本上,这就是我们应用程序的起点。
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.set('view engine', 'pug');
app.set('views', 'views');
const adminData = require('./routes/admin');
const shopRoutes = require('./routes/shop');
app.use(bodyParser.urlencoded({extended: false}));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/admin', adminData.routes);
app.use(shopRoutes);
app.use((req, res, next) => {
res.render('404', {pageTitle: "Page Not Found"})
});
app.listen(3000);
这里最重要的事情是配置Pug作为我们的视图引擎。我们通过使用app.set() 函数来做到这一点。基本上,app.set() 函数是用来为我们的应用程序全局设置任何值。在这个例子中,我们用它来设置view engine 属性为pug 。
同时,我们将views 属性设置为views (基本上是我们的视图文件所在的文件夹的名称)。顺便说一下,默认的视图文件夹也是views 。因此,我们可以不明确地设置它。然而,如果我们决定使用一个不同的文件夹名称,我们需要使用app.set() 来设置。
接下来,我们将为商店相关的路由创建一个路由器。
const path = require('path');
const express = require('express');
const adminData = require('./admin');
const router = express.Router();
router.get('/', (req, res, next) => {
const products = adminData.products;
res.render('shop', { prods: products, pageTitle: 'Shop' });
});
module.exports = router;
这个文件中主要需要注意的是对res.render() 函数的调用。该函数接收模板的名称(在本例中是shop)。第二个属性是一个包含prods 属性和pageTitle 属性的对象。这些属性将在Pug模板中可用。
第二个路由器是用于管理页面(或添加产品)。
const path = require('path');
const express = require('express');
const router = express.Router();
const products = [];
router.get('/add-product', (req, res, next) => {
res.render('add-product', {pageTitle: 'Add Product'});
});
router.post('/add-product', (req, res, next) => {
products.push({ title: req.body.title });
res.redirect('/');
});
exports.routes = router;
exports.products = products;
这里,我们有几个路由。其中一个是用于显示添加产品页面的GET方法处理程序。在这种情况下,我们渲染add-product 模板。
第二个是用于添加新产品的POST方法处理程序。每当用户提交表单时,我们就推送一个新产品到products 数组。
5.NodeJS Express Pug可重复使用的布局
你可能已经注意到,我们在上面的代码中重复了一堆的模板代码。基本上,导航栏在两个模板中都存在,尽管它实际上是相同的。此外,我们还有一些共同的CSS导入。如果我们能删除这些重复的模板代码,可能会有利于我们代码的可维护性。
我们可以通过使用Pug的可重用布局功能来做到这一点。为了使用这个功能,我们将在views 文件夹中创建另一个文件夹layouts 。在这个文件夹中,我们将创建一个特殊的模板文件,称为main-layout.pug 。
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title #{pageTitle}
link(rel="stylesheet", href="/css/main.css")
block styles
body
header.main-header
nav.main-header__nav
ul.main-header__item-list
li.main-header__item
a(href="/", class=(path === '/' ? 'active' : '')) Shop
li.main-header__item
a(href="/admin/add-product", class=(path === '/admin/add-product' ? 'active' : '')) Add Product
block content
除了存在特殊的block 部分外,这与其他模板基本相同。我们有block styles 和block content 。block 这个关键词表明,实际内容将来自其他模板。
此外,由于我们现在有一个共同的导航栏,我们需要动态地确定活动的导航项目。为此,我们有一些特殊的逻辑,同时使用传入的path 属性应用class 属性。
我们现在可以在shop 和add-product 模板中使用main-layout 可重复使用的模板。
extends layouts/main-layout.pug
block styles
link(rel="stylesheet", href="/css/product.css")
block content
main
if prods.length > 0
.grid
each product in prods
article.card.product-item
header.card__header
h1.product_title #{product.title}
.card__image
img(src="https://cdn.pixabay.com/photo/2015/11/19/21/10/glasses-1052010__340.jpg" alt="Product Image")
.card__content
h2.product__price $9.99
p2.product_description The Best Book Ever Written
.card__actions
button.btn Add To Cart
else
h1 No Products
正如你所看到的,这里我们使用extends 关键字来扩展main-layout.pug 。此外,我们还定义了块状部分的实际内容。块状部分由它们各自的名字指定。
下面是add-product.pug 模板文件的修订代码。
extends layouts/main-layout.pug
block styles
link(rel="stylesheet", href="/css/forms.css")
link(rel="stylesheet", href="/css/product.css")
block content
main
form.product-form(action="/admin/add-product", method="POST")
.form-control
label(for="title") Title
input(type="text", name="title", id="title")
button.btn(type="submit") Add Product
为了支持更新的Pug模板,我们还需要在我们的路由器文件中传递额外的path 属性。这个属性将有助于确定哪个导航项应该得到active 的CSS类。
router.get('/', (req, res, next) => {
const products = adminData.products;
res.render('shop', { prods: products, pageTitle: 'Shop', path: '/' });
});
router.get('/add-product', (req, res, next) => {
res.render('add-product', {pageTitle: 'Add Product', path: '/admin/add-product'});
});
总结
通过这些,我们已经成功地学习了如何使用Express Pug视图引擎在NodeJS中执行模板化。我们还研究了如何创建可重用的Pug模板,以减少模板内的代码重复。
上述示例应用程序的代码可以在GitHub上找到。该应用中使用的所有CSS文件和类都在GitHub repo中,供参考。
如果你对这篇文章有任何评论或疑问,请随时在下面的评论区提及。