如果您正在使用node编写后端应用程序。如果您想将HTML发送回与之交互的客户端,那么您必须找到一种方法来“混合”,或者将处理后的数据插入到您正在发送的HTML文件中。
出于简单的数据插值和测试目的,一种方法是将HTML字符串与数据连接起来,或者在JavaScript中使用模板字符串。但是,如果您想编写具有重要HTML代码和动态内容的应用程序,最好使用为此目的而设计的工具,如模板引擎。
EJS(嵌入式JavaScript模板)是最流行的JavaScript模板引擎之一。顾名思义,它允许我们将JavaScript代码嵌入到模板语言中,然后用于生成HTML。
在本文中,我将向您详细介绍如何使用EJS模板化node应用程序。首先,我们将介绍模板引擎和设置EJS的基础知识,但我们将继续介绍在node中使用EJS的更高级指南。
模板引擎
根据维基百科的定义,模板引擎是一种将模板与数据模型结合起来生成的代码,在我们的例子中,它可以生成真实的HTML代码。
模板引擎处理将数据插入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;我将很快解释为什么我们需要这些文件夹。
现在,在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,您应该会看到以下结果:
现在,让我们浏览一下代码的一些部分,并了解发生了什么。
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,您应该得到以下信息:
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 你应该看到以下内容:
我们向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">© 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 你应该看到:
通过导入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/
请注意,我们可以在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语法参考。