模板引擎 EJS

EJS(Embedded JavaScript)是一种简单而强大的模板引擎,它允许你在 HTML 中嵌入 JavaScript 代码,动态生成页面内容。作为 Express 中最常用的模板引擎之一,EJS 易于上手且功能齐全。本章将详细介绍如何在 Express 应用中集成和使用 EJS。

1. 什么是 EJS?

EJS 是一种模板语言,它使用 <% %> 标签嵌入 JavaScript 代码,用 <%= %> 输出变量值。它不会强制执行任何特定的组织方式(如继承、布局等),但通过 include 功能可以轻松实现代码复用。EJS 的语法非常接近纯 HTML,学习成本低。

EJS 的特点:

  • 使用纯 JavaScript 代码。
  • 快速编译与渲染。
  • 支持自定义分隔符。
  • 支持视图局部(partials)和布局(layouts)。
  • 丰富的文档和社区支持。

2. 安装与配置 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 文件的渲染。

3. 创建第一个 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 发送给客户端。

4. EJS 基本语法

EJS 提供了多种标签用于嵌入 JavaScript 代码:

  • <% %>:执行 JavaScript 代码(不输出)。
  • <%= %>:输出转义后的 HTML 内容(防止 XSS)。
  • <%- %>:输出原始 HTML(不转义)。
  • <%# %>:注释标签(不输出)。
  • <%%%%>:输出字面量 <%%>

4.1 输出变量

<p>用户名:<%= user.name %></p>

4.2 执行 JavaScript 代码

<% if (user) { %>
  <p>欢迎回来,<%= user.name %>!</p>
<% } else { %>
  <p>请先登录。</p>
<% } %>

4.3 循环遍历数组

<ul>
  <% users.forEach(user => { %>
    <li><%= user.name %> - <%= user.email %></li>
  <% }); %>
</ul>

4.4 输出原始 HTML

如果变量中包含 HTML 标签,并且希望浏览器解析它们,使用 <%- %>

<div><%- richText %></div>
安全提示: 使用 <%- %> 输出未转义的内容可能存在 XSS 风险,确保内容是可信的,或者在使用前进行清理。

5. 局部模板(Partials)

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>版权所有 &copy; 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。

6. 布局(Layouts)

EJS 本身不提供内置的布局系统,但可以通过组合 include 和变量来实现类似布局的功能。另一种常见方法是使用 express-ejs-layouts 中间件。

6.1 使用 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 %> 位置。

7. 向模板传递数据

在路由中通过 res.render(view, locals) 的第二个参数传递数据。locals 是一个对象,其属性在模板中作为变量使用。

res.render('profile', {
  user: { name: '张三', age: 30 },
  posts: [
    { title: '第一篇文章', content: '...' },
    { title: '第二篇文章', content: '...' }
  ]
});

在模板中可以直接使用 user.nameposts 等。

此外,还可以通过 app.localsres.locals 设置全局变量,在所有模板中可用。

8. 自定义分隔符

如果你希望更改 EJS 的标签符号(例如避免与前端框架冲突),可以在配置时指定:

const ejs = require('ejs');
// 设置全局分隔符
ejs.delimiter = '?';
// 或者针对某个渲染
res.render('page', { data }, { delimiter: '?' });

现在模板中应使用 <?= ?> 等。

9. 在模板中使用辅助函数

可以通过 locals 传递函数,或者在 app.locals 中定义全局辅助函数。

// 在 app.js 中
app.locals.formatDate = (date) => {
  return new Date(date).toLocaleDateString('zh-CN');
};

在模板中直接调用:

<p>发布日期:<%= formatDate(post.date) %></p>

10. 完整示例:博客文章列表

下面是一个完整的示例,展示如何使用 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>

11. 注意事项与最佳实践

  • 避免在模板中编写复杂逻辑:将数据处理放在控制器或辅助函数中,保持模板简洁。
  • 注意 XSS 防护:默认 <%= %> 会转义 HTML 特殊字符,使用 <%- %> 时确保内容安全。
  • 缓存设置:在生产环境中,EJS 会缓存编译后的模板以提高性能,无需额外配置。
  • 文件扩展名:虽然 EJS 默认使用 .ejs,但可以通过 app.engine() 更改,例如使用 .html
总结: EJS 是一个简单、灵活的模板引擎,与 Express 无缝集成。通过本章的学习,你应该能够熟练创建 EJS 模板,使用变量、循环、条件判断,以及利用 include 和布局实现代码复用。接下来,你可以探索更高级的主题,如与数据库集成、表单处理等。