EJS(Embedded JavaScript)是一种简单而强大的模板引擎,它允许你在 HTML 中嵌入 JavaScript 代码,动态生成页面内容。作为 Express 中最常用的模板引擎之一,EJS 易于上手且功能齐全。本章将详细介绍如何在 Express 应用中集成和使用 EJS。
EJS 是一种模板语言,它使用 <% %> 标签嵌入 JavaScript 代码,用 <%= %> 输出变量值。它不会强制执行任何特定的组织方式(如继承、布局等),但通过 include 功能可以轻松实现代码复用。EJS 的语法非常接近纯 HTML,学习成本低。
EJS 的特点:
在 Express 项目中使用 EJS 非常简单。首先安装 ejs 包:
npm install ejs
然后在主应用文件(如 app.js)中设置模板引擎和视图目录:
const express = require('express');
const path = require('path');
const app = express();
// 设置模板引擎为 EJS
app.set('view engine', 'ejs');
// 设置视图文件所在目录(默认为 ./views)
app.set('views', path.join(__dirname, 'views'));
// 静态文件服务(可选)
app.use(express.static(path.join(__dirname, 'public')));
安装并配置后,Express 会自动加载 EJS 模块并处理 .ejs 文件的渲染。
在项目根目录下创建 views 文件夹,并在其中新建一个 index.ejs 文件:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= message %></h1>
</body>
</html>
在路由中使用 res.render() 渲染该模板并传递数据:
app.get('/', (req, res) => {
res.render('index', {
title: '我的 EJS 页面',
message: 'Hello from EJS!'
});
});
当访问根路径时,EJS 会将模板中的 <%= title %> 和 <%= message %> 替换为传入的值,生成最终的 HTML 发送给客户端。
EJS 提供了多种标签用于嵌入 JavaScript 代码:
<% %>:执行 JavaScript 代码(不输出)。<%= %>:输出转义后的 HTML 内容(防止 XSS)。<%- %>:输出原始 HTML(不转义)。<%# %>:注释标签(不输出)。<%% 和 %%>:输出字面量 <% 或 %>。<p>用户名:<%= user.name %></p>
<% if (user) { %>
<p>欢迎回来,<%= user.name %>!</p>
<% } else { %>
<p>请先登录。</p>
<% } %>
<ul>
<% users.forEach(user => { %>
<li><%= user.name %> - <%= user.email %></li>
<% }); %>
</ul>
如果变量中包含 HTML 标签,并且希望浏览器解析它们,使用 <%- %>:
<div><%- richText %></div>
<%- %> 输出未转义的内容可能存在 XSS 风险,确保内容是可信的,或者在使用前进行清理。
EJS 支持使用 include 函数包含其他模板文件,实现代码复用。例如,将页头、页脚提取为独立的文件。
创建 views/partials/header.ejs:
<header>
<h1>我的网站</h1>
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>
</header>
创建 views/partials/footer.ejs:
<footer>
<p>版权所有 © 2025</p>
</footer>
在主页模板中引入它们:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<%- include('partials/header') %>
<main>
<h2>主页内容</h2>
</main>
<%- include('partials/footer') %>
</body>
</html>
include 函数接受文件的相对路径(相对于当前模板文件所在的目录),并返回包含的内容。注意使用 <%- %> 输出,因为可能包含 HTML。
EJS 本身不提供内置的布局系统,但可以通过组合 include 和变量来实现类似布局的功能。另一种常见方法是使用 express-ejs-layouts 中间件。
安装中间件:
npm install express-ejs-layouts
在应用中使用:
const express = require('express');
const expressLayouts = require('express-ejs-layouts');
const app = express();
app.set('view engine', 'ejs');
app.use(expressLayouts);
app.set('layout', 'layout'); // 默认布局文件 views/layout.ejs
创建 views/layout.ejs 作为主布局:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<%- body %>
</body>
</html>
在具体视图(如 views/index.ejs)中只需写页面内容:
<h1>首页</h1>
<p>欢迎访问我的网站!</p>
渲染时:
res.render('index', { title: '首页' });
最终生成的 HTML 会将视图内容嵌入布局中的 <%- body %> 位置。
在路由中通过 res.render(view, locals) 的第二个参数传递数据。locals 是一个对象,其属性在模板中作为变量使用。
res.render('profile', {
user: { name: '张三', age: 30 },
posts: [
{ title: '第一篇文章', content: '...' },
{ title: '第二篇文章', content: '...' }
]
});
在模板中可以直接使用 user.name、posts 等。
此外,还可以通过 app.locals 和 res.locals 设置全局变量,在所有模板中可用。
如果你希望更改 EJS 的标签符号(例如避免与前端框架冲突),可以在配置时指定:
const ejs = require('ejs');
// 设置全局分隔符
ejs.delimiter = '?';
// 或者针对某个渲染
res.render('page', { data }, { delimiter: '?' });
现在模板中应使用 <?= ?> 等。
可以通过 locals 传递函数,或者在 app.locals 中定义全局辅助函数。
// 在 app.js 中
app.locals.formatDate = (date) => {
return new Date(date).toLocaleDateString('zh-CN');
};
在模板中直接调用:
<p>发布日期:<%= formatDate(post.date) %></p>
下面是一个完整的示例,展示如何使用 EJS 渲染博客文章列表。
app.js
const express = require('express');
const path = require('path');
const app = express();
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.static(path.join(__dirname, 'public')));
// 模拟数据
const posts = [
{ id: 1, title: '第一篇博客', content: '这是第一篇博客的内容。', date: new Date() },
{ id: 2, title: '第二篇博客', content: '这是第二篇博客的内容。', date: new Date() }
];
app.get('/', (req, res) => {
res.render('index', { title: '首页', posts });
});
app.get('/post/:id', (req, res) => {
const post = posts.find(p => p.id === parseInt(req.params.id));
if (post) {
res.render('post', { title: post.title, post });
} else {
res.status(404).send('文章不存在');
}
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
views/layout.ejs(使用 express-ejs-layouts)
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<h1><a href="/">我的博客</a></h1>
</header>
<main>
<%- body %>
</main>
<footer>
<p>© 2025 我的博客</p>
</footer>
</body>
</html>
views/index.ejs
<h2>最新文章</h2>
<% if (posts.length) { %>
<ul>
<% posts.forEach(post => { %>
<li>
<a href="/post/<%= post.id %>"><%= post.title %></a>
<small><%= new Date(post.date).toLocaleDateString() %></small>
</li>
<% }); %>
</ul>
<% } else { %>
<p>暂无文章。</p>
<% } %>
views/post.ejs
<h2><%= post.title %></h2>
<p>发布于:<%= new Date(post.date).toLocaleDateString() %></p>
<div>
<%= post.content %>
</div>
<p><a href="/">返回首页</a></p>
<%= %> 会转义 HTML 特殊字符,使用 <%- %> 时确保内容安全。.ejs,但可以通过 app.engine() 更改,例如使用 .html。include 和布局实现代码复用。接下来,你可以探索更高级的主题,如与数据库集成、表单处理等。