在Node.js中使用Express Pug视图引擎进行模板制作

805 阅读7分钟

在Node.js中使用Express Pug视图引擎进行模板设计

每个流行的模板引擎都有自己的定义占位符的理念。在这篇文章中,我们来看看Pug以及如何在NodeJS和Express中使用它。

在我们的HTML页面中呈现动态内容是一个常见的要求。模板引擎是支持这一功能的一个好方法。在这篇文章中,我们将学习如何使用Express Pug视图引擎在NodeJS中执行模板化

如果你是Express的新手,可以看看这篇关于ExpressJS入门的文章。

1.什么是模板引擎?

模板引擎有助于使我们的Web应用程序在数据方面具有动态性。在模板引擎的核心,有一个HTML模板,上面有动态数据的占位符。

当一个请求被我们的应用程序处理时,模板引擎就开始行动。基本上,该引擎将占位符或片段替换为实际的HTML内容。换句话说,模板引擎执行即时生成的HTML,并将其发送到客户端。

一些最流行的模板引擎是:

每个引擎都有自己的定义占位符的理念。在这篇文章中,我们将看看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 stylesblock contentblock 这个关键词表明,实际内容将来自其他模板。

此外,由于我们现在有一个共同的导航栏,我们需要动态地确定活动的导航项目。为此,我们有一些特殊的逻辑,同时使用传入的path 属性应用class 属性。

我们现在可以在shopadd-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中,供参考。

如果你对这篇文章有任何评论或疑问,请随时在下面的评论区提及。