1.17 模板引擎EJS
在 Node.js 中,模板引擎用于将动态数据与静态模板结合,生成最终的 HTML 页面。
一、核心概念
1. 模板引擎的作用
- 将动态数据(如数据库查询结果)注入到静态模板中。
- 分离视图逻辑与业务逻辑。
- 提供安全的 HTML 转义,防止 XSS 攻击。
2. 基本工作流程
- 定义模板:创建包含占位符的 HTML 文件。
- 准备数据:从数据库或 API 获取动态数据。
- 渲染模板:将数据与模板结合,生成最终 HTML。
- 返回响应:将 HTML 发送给客户端。
二、主流模板引擎对比
特性 | EJS | Pug (Jade) | Handlebars | Nunjucks |
---|---|---|---|---|
语法风格 | HTML 内嵌标签 | 缩进式语法 | 双大括号语法 | 类似 Jinja2 |
学习曲线 | 低(HTML 友好) | 中等 | 低 | 中等 |
性能 | 高 | 高 | 中等 | 高 |
生态系统 | 丰富 | 丰富 | 丰富 | 中等 |
三、EJS(Embedded JavaScript)
1. 特点
- 语法与 HTML 高度兼容。
- 使用
<% %>
和<%= %>
标签嵌入 JavaScript。 - 支持条件语句、循环和自定义函数。
2. 安装与使用
npm install ejs
const express = require('express');
const app = express();// 设置 EJS 为视图引擎
app.set('view engine', 'ejs');// 渲染模板
app.get('/', (req, res) => {const data = {title: 'EJS 示例',users: ['Alice', 'Bob', 'Charlie']};res.render('index', data); // 渲染 views/index.ejs
});
3. 模板示例(index.ejs)
<!DOCTYPE html>
<html>
<head><title><%= title %></title>
</head>
<body><h1><%= title %></h1><ul><% users.forEach(user => { %><li><%= user %></li><% }); %></ul><% if (users.length > 2) { %><p>用户数量超过 2 人</p><% } %>
</body>
</html>
安全与性能考虑
1. HTML 转义
- 自动转义:大多数模板引擎默认转义 HTML 特殊字符(如
<
转为<
),防止 XSS 攻击。<!-- EJS 示例 --> <p><%= userInput %></p> <!-- 自动转义 --> <p><%- userInput %></p> <!-- 不转义(危险!) -->
2. 性能优化
-
缓存编译:生产环境中启用模板缓存。
// Express 配置缓存 app.set('view cache', process.env.NODE_ENV === 'production');
-
避免复杂逻辑:将业务逻辑放在控制器中,保持模板简单。
四、EJS详细使用介绍
一、核心概念
1. 模板引擎的作用
- 将动态数据(如数据库查询结果)与静态 HTML 模板结合。
- 分离视图逻辑与业务逻辑,提高代码可维护性。
2. EJS 的特点
- 语法简单:直接在 HTML 中嵌入 JavaScript,无需学习新语法。
- 灵活性高:支持所有 JavaScript 表达式和逻辑。
- 性能优异:编译后的模板执行速度快。
- 与 Express 集成良好:是 Express 官方推荐的模板引擎之一。
二、基础语法
1. 输出变量:<%= ... %>
<!-- 渲染变量 -->
<h1>Hello, <%= username %>!</h1> <!-- 输出变量值,自动转义 HTML --><!-- 执行表达式 -->
<p>2 + 2 = <%= 2 + 2 %></p>
2. 执行 JavaScript 代码:<% ... %>
<!-- 条件判断 -->
<% if (user.isAdmin) { %><p>管理员视图</p>
<% } else { %><p>普通用户视图</p>
<% } %><!-- 循环 -->
<ul><% users.forEach(user => { %><li><%= user.name %></li><% }); %>
</ul>
3. 不转义输出:<%- ... %>
<!-- 输出 HTML 代码(危险!需确保内容安全) -->
<div><%- htmlContent %></div>
三、与 Express 集成
1. 安装与配置
npm install ejs
const express = require('express');
const app = express();// 设置 EJS 为视图引擎
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views'); // 视图文件目录// 渲染模板
app.get('/', (req, res) => {res.render('index', {title: 'EJS 示例',users: [{ id: 1, name: 'Alice' },{ id: 2, name: 'Bob' },{ id: 3, name: 'Charlie' }]});
});
2. 目录结构
project/
├── app.js
├── package.json
└── views/├── index.ejs└── partials/└── header.ejs
四、高级用法
1. 包含子模板:<%- include() %>
<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head><title><%= title %></title><%- include('partials/header') %> <!-- 包含 header.ejs -->
</head>
<body><h1><%= title %></h1><%- include('partials/users', { users }) %> <!-- 传递数据到子模板 -->
</body>
</html>
2. 自定义过滤器
// app.js 中注册过滤器
app.locals.formatDate = (date) => {return new Date(date).toLocaleDateString();
};
<!-- 在模板中使用 -->
<p>创建时间:<%= formatDate(post.createdAt) %></p>
3. 布局模板(通过自定义实现)
// app.js 中添加布局功能
app.use((req, res, next) => {res.renderWithLayout = (view, data) => {data = data || {};data.content = () => include(view);res.render('layouts/main', data);};next();
});
<!-- layouts/main.ejs -->
<!DOCTYPE html>
<html>
<head><title><%= title %></title>
</head>
<body><header>导航栏</header><main><%- content() %></main> <!-- 插入子视图 --><footer>页脚</footer>
</body>
</html>
五、安全考虑
1. HTML 转义
- 默认转义:使用
<%= ... %>
自动转义 HTML 特殊字符(如<
转为<
),防止 XSS 攻击。 - 不转义:仅在信任内容时使用
<%- ... %>
,如:<%- trustedHtml %> <!-- 仅用于安全的 HTML 内容 -->
2. 数据验证
- 在渲染前验证和清理数据,避免恶意内容:
app.get('/profile', (req, res) => {const username = req.query.username.replace(/[^a-zA-Z0-9]/g, ''); // 清理输入res.render('profile', { username }); });
六、性能优化
1. 模板缓存
- 在生产环境中启用模板缓存:
app.set('view cache', process.env.NODE_ENV === 'production');
2. 预编译模板
- 使用
ejs.compile()
预编译模板,减少运行时开销:const template = ejs.compile(fs.readFileSync('template.ejs', 'utf8')); const html = template({ data });
七、常见问题与解决方案
1. 变量未定义错误
- 确保传递所有必要变量到模板:
res.render('profile', { user: req.user }); // 确保 user 存在
2. 长逻辑处理
- 将复杂逻辑移至控制器或辅助函数,保持模板简洁:
// app.js app.locals.getTotal = (items) => {return items.reduce((sum, item) => sum + item.price, 0); };
3. 异步数据加载
- 在渲染前确保所有数据已加载:
app.get('/posts', async (req, res) => {const posts = await Post.find();res.render('posts', { posts }); });
八、完整示例
1. 模板文件(views/index.ejs)
<!DOCTYPE html>
<html>
<head><title><%= title %></title>
</head>
<body><h1><%= title %></h1><%- include('partials/message', { message }) %><ul><% users.forEach(user => { %><li><strong><%= user.name %></strong><p><%= user.email %></p><% if (user.isAdmin) { %><span class="badge">管理员</span><% } %></li><% }); %></ul>
</body>
</html>
2. 控制器(app.js)
app.get('/', (req, res) => {const users = [{ name: 'Alice', email: 'alice@example.com', isAdmin: true },{ name: 'Bob', email: 'bob@example.com', isAdmin: false }];res.render('index', {title: '用户列表',users,message: '欢迎查看用户列表'});
});
总结
EJS 的核心优势在于 简单直接 和 与 JavaScript 的无缝集成,适合需要快速开发、灵活控制渲染逻辑的项目。通过合理使用模板包含、过滤器和布局,可以构建结构清晰、可维护的视图层。
推荐场景:
- 前后端不分离的 Web 应用。
- 对性能有要求的项目(编译后的模板执行效率高)。
- 团队熟悉 JavaScript/HTML,希望降低学习成本。