使用EJS作为node的模板

1,151 阅读6分钟

如果您正在使用node编写后端应用程序。如果您想将HTML发送回与之交互的客户端,那么您必须找到一种方法来“混合”,或者将处理后的数据插入到您正在发送的HTML文件中。

出于简单的数据插值和测试目的,一种方法是将HTML字符串与数据连接起来,或者在JavaScript中使用模板字符串。但是,如果您想编写具有重要HTML代码和动态内容的应用程序,最好使用为此目的而设计的工具,如模板引擎。

EJS(嵌入式JavaScript模板)是最流行的JavaScript模板引擎之一。顾名思义,它允许我们将JavaScript代码嵌入到模板语言中,然后用于生成HTML。

在本文中,我将向您详细介绍如何使用EJS模板化node应用程序。首先,我们将介绍模板引擎和设置EJS的基础知识,但我们将继续介绍在node中使用EJS的更高级指南。

模板引擎

根据维基百科的定义,模板引擎是一种将模板与数据模型结合起来生成的代码,在我们的例子中,它可以生成真实的HTML代码。

image.png

模板引擎处理将数据插入HTML代码的任务,同时提供一些通过串接字符串很难复制的特性(如EJS中的片段)。

介绍EJS

如前所述,EJS是最流行的JavaScript模板引擎之一。选择它的原因之一是EJS代码看起来像纯HTML。

它保留了HTML的语法,同时允许数据插值,这与Pug(另一个模板引擎)不同,后者对缩进和空格使用不同的语法。

EJS文件是使用.ejs文件扩展名保存的。

如何在nodejs应用程序中设置EJS->使用Express

我们将在本教程中使用Express,因为它是最好的node框架之一。它是极简主义的,很容易开始。

让我们从头开始一个项目。创建一个新文件夹,用于放置项目文件。

通过在终端中运行 npm init-y 初始化文件夹中的新node项目,然后要安装Express和EJS,请运行:

npm install -S express ejs

安装后,创建一个app.js文件和根文件夹中的views文件夹。在views文件夹内,创建两个文件夹pages和partials;我将很快解释为什么我们需要这些文件夹。

image.png

现在,在views/pages文件夹中创建一个名为index.ejs的文件 首先,将以下内容复制到app.js中

const express = require('express')
const app = express()
const port = 3000

app.set('view engine', 'ejs')

app.get('/', (req, res) => {
    res.render('pages/index')
})
app.listen(port, () => {
  console.log(`App listening at port ${port}`)
})

并将以下内容复制到index.ejs:

<h1>Hi, there!</h1>

如果您运行node app.js。从根文件夹访问终端上的js,然后访问localhost:3000,您应该会看到以下结果:

1.PNG

现在,让我们浏览一下代码的一些部分,并了解发生了什么。

app.set('view engine','ejs')是不言自明的。我们将EJS设置为Express app view引擎。默认情况下,Express在解析模板文件时会查看views文件夹的内部,这就是我们必须创建views文件夹的原因。

在res.render('pages/index')中,我们在响应对象上调用render方法。这将呈现提供的视图(本例中为pages/index),并将呈现的HTML字符串发送回客户端。

注意,我们不必提供文件扩展名,因为Express会自动解析文件扩展名;它知道我们在应用程序中使用的视图引擎app.set('view engine','ejs')。我们也不必将路径写为views/pages/index,因为默认情况下使用views文件夹。

传递数据以进行渲染

回想一下,我们的目标是将数据与模板相结合。我们可以通过将第二个参数传递给res.render来实现这一点。第二个参数必须是对象,可以在EJS模板文件中访问。

更新app.js。就像这样:

const express = require('express')
const app = express()
const port = 3000
app.set('view engine', 'ejs')

const user = {
    firstName: 'Tim',
    lastName: 'Cook',
}
app.get('/', (req, res) => {
    res.render('pages/index', {
        user: user
    })
})
app.listen(port, () => {
  console.log(`App listening at port ${port}`)
})

同时更新index.ejs:

<h1>Hi, I am <%= user.firstName  %></h1>

运行 node app.js,您应该得到以下信息:

2.PNG

EJS语法

您刚刚了解了EJS的基本语法。语法如下所示:

<startingTag content closingTag>

这里的语法是 <%= user.firstName %>

EJS有不同的标签用于不同的目的。此开始标记<%=称为“转义输出”标记,因为如果内容中的字符串包含禁止的字符,如>和&,则输出字符串中的字符将被转义(替换为HTML代码)。

EJS中的逻辑

EJS名副其实,因为我们提供了标签<%,可以包含逻辑(称为“scriptlet”)作为其内容。此标记中可以使用任何JavaScript语法。

要查看此操作,请更新app.js:

const express = require('express')
const app = express()
const port = 3000
app.set('view engine', 'ejs')
const user = {
    firstName: 'Tim',
    lastName: 'Cook',
    admin: true,
}
app.get('/', (req, res) => {
    res.render('pages/index', {
        user
    })
})
app.listen(port, () => {
  console.log(`App listening at port ${port}`)
})

然后更新index.js:

<h1>Hi, I am <%= user.firstName  %></h1>
<% if (user.admin) { %>
    <p>Let me tell you a secret: <b>I am an admin</b></p>
<% } %>

如果运行app.js,您将看到显示的If语句中的段落。 在用户对象中更改admin:false,则不会显示段落。

注意scriptlet的语法<%if(user.admin){%>。开始{在开始和结束标记之间捕获,结束}在单独的开始和结束标记中捕获。

循环浏览数据

因为<%标记可以包含任何有效的JavaScript代码,所以我们还可以在EJS中循环并显示数据。在名为articles的views/pages内创建新文件articles.ejs。

接下来,更新app.js:

const express = require('express')
const app = express()
const port = 3000
app.set('view engine', 'ejs')

const posts = [
    {title: 'Title 1', body: 'Body 1' },
    {title: 'Title 2', body: 'Body 2' },
    {title: 'Title 3', body: 'Body 3' },
    {title: 'Title 4', body: 'Body 4' },
]
const user = {
    firstName: 'Tim',
    lastName: 'Cook',
}
app.get('/', (req, res) => {
    res.render('pages/index', {
        user
    })
})
app.get('/articles', (req, res) => {
    res.render('pages/articles', {
        articles: posts
    })
})
app.listen(port, () => {
  console.log(`App listening at port ${port}`)
})

articles.ejs也更新:

<!DOCTYPE html>
<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>Articles</title>
</head>
<body>
    <ul>
        <% articles.forEach((article)=> { %>
            <li>
                <h2>
                    <%= article.title %>
                </h2>
                <p>
                    <%= article.body %>
                </p>
            </li>
            <hr />
        <% }) %>
    </ul>
</body>
</html>

运行app时,请访问http://localhost:3000/articles 你应该看到以下内容: 3.PNG

我们向articles传递了一个包含标题和正文的post对象数组articles.ejs模板。然后,在模板中,我们使用forEach在数组中循环,以将每个post对象呈现为列表项。

请注意,在循环的每次迭代中引用数组中每个项的article变量<% articles.forEach((article)=> { %>可以在模板代码的其他部分中访问,直到我们到达结束括号的末尾<% }) %>.

EJS部分

网站的某些部分在不同的页面上保持不变,比如页眉、页脚和侧边栏。EJS为我们提供了允许我们重用视图的部分。

回想一下,我们之前创建了views/partials文件夹。创建两个名为head.ejs和footer.ejs的新文件在这个文件夹中。

head.ejs内容如下所示:

<!DOCTYPE html>
<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">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

    <title>Articles</title>
</head>

我在head.ejs中包含了一个Bootstrap链接,因为我将使用Bootstrap examples page.中的代码示例。

现在,更新footer.js,像这样:

<div class="container">
  <footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4">
      <p class="col-md-4 mb-0 text-muted">&copy; 2021 Simple Blog</p>
      <ul class="nav col-md-4 justify-content-end">
          <li class="nav-item"><a href="/" class="nav-link px-2">Home</a></li>
          <li class="nav-item"><a href="/articles" class="nav-link px-2">Articles</a></li>
          <li class="nav-item"><a href="#" class="nav-link px-2">About</a></li>
      </ul>
  </footer>
</div>

articles.ejs:

<%- include('../partials/head') %>
<body>
    <ul>
        <% articles.forEach((article)=> { %>
            <li>
                <h2>
                    <%= article.title %>
                </h2>
                <p>
                    <%= article.body %>
                </p>
            </li>
            <hr />
        <% }) %>
    </ul>
</body>
<%- include('../partials/footer') %>

我已经包括了head.ejs和footer.ejs partials使用include函数的。

它以文件的相对路径作为参数。因为pages和partials在同一个文件夹中,所以要从pages访问partials,我们必须首先走出pages文件夹(../partials/head')。

另外,请注意使用的EJS标记(<%-)而不是上面提到的转义输出标记(<%=)。<%-被称为“unescaped output”标记,在您想要输出原始HTML时使用。

一定要小心使用。不要将其与用户输入一起使用,因为它会使您的应用程序暴露于攻击之下。

让我们创建另一个部分命名header.ejs位于views/partials的内部。以下是header.ejs:

<header class="p-3 bg-dark text-white mb-4">
    <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
            <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                <li><a href="/" class="nav-link px-2 text-white">Home</a></li>
                <li><a href="/articles" class="nav-link px-2 text-white">Articles</a></li>
                <li><a href="#" class="nav-link px-2 text-white">About</a></li>
            </ul>
            <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3">
                <input type="search" class="form-control form-control-dark" placeholder="Search..." aria-label="Search">
            </form>
            <div class="text-end">
                <button type="button" class="btn btn-outline-light me-2">Login</button>
                <button type="button" class="btn btn-warning">Sign-up</button>
            </div>
        </div>
    </div>
</header>

更新articles.ejs:

<%- include('../partials/head') %>
<%- include('../partials/header') %>
<body>
    <ul>
        <% articles.forEach((article)=> { %>
            <li>
                <h2>
                    <%= article.title %>
                </h2>
                <p>
                    <%= article.body %>
                </p>
            </li>
            <hr />
        <% }) %>
    </ul>
</body>
<%- include('../partials/footer') %>

运行node app.js,访问http://localhost:3000/articles 你应该看到: 4.PNG

通过导入partials,我们将header和bootstrap样式包含到articles页面中。我们也可以在其他页面上使用这些部分。

我们还将更新index.ejs包括以下部分:

<%- include('../partials/head') %>
<%- include('../partials/header') %>
<div class="container">
    <h1>Hi, I am <%= user.firstName  %> <%= user.lastName %></h1>
    <h2>Welcome to my Blog</h2>
</div>

<%- include('../partials/footer') %>

再次运行app.js,你可以在http://localhost:3000/

5.PNG

请注意,我们可以在EJS标记中使用任何JavaScript操作符,因此我们可以编写以下代码:

<h1>Hi, I am <%= user.firstName + " " + user.lastName  %>

将数据传递给partials

index页上有问题,你能看到吗?

这一页的标题是Articles,因为标题是head.ejs partial具有硬编码的网页标题,这是不可取的。

我们希望页面的标题反映页面的内容,所以我们必须将标题作为参数传递。

EJS使它变得简单,因为分部可以访问父视图中的每个变量,所以我们只需在调用res.render的同时传递对象中的变量。

在app.js中更新对res.render的调用。具体如下:

app.get('/', (req, res) => {
    res.render('pages/index', {
        user,
        title: "Home Page"
    })
})
app.get('/articles', (req, res) => {
    res.render('pages/articles', {
        articles: posts,
        title: "Articles"
    })
})

然后更新head.ejs中的标题:

<title> 
        <%= title %>  
</title>

再次运行app.js,每个页面都应该有正确的标题。 当包含变量时,也可以将其传递给分部,如下所示:

<%- include('../partials/head', {title :'Page Title'}) %>

以这种方式传递的变量优先于通过render传递的变量。

无论是选择通过父视图将变量传递给分部,还是在调用包含期间,都必须确保变量存在,然后才能尝试在分部中使用它。如果它不存在,则会引发异常。

为了避免这种情况,您可以在partial中为变量提供一个默认值,例如,我们可以更新head.ejs如下:

 <title> 
        <%= typeof title != 'undefined' ? title : 'Page Title' %>  
 </title>

我们使用三元运算符来检查title是否undefined,如果未定义,则提供默认值Page title。

总结

在本文中,我们回顾了模板引擎,并介绍了用于JavaScript的EJS以及如何使用它。我们已经了解了如何重用带有片段的代码,以及如何将数据传递给它们。 如果您想了解更多关于EJS可能实现的功能,这里是EJS语法参考