EJS 模板引擎
后端模板 EJS Express.js一、什么是 EJS
1.1 定义
EJS = Embedded JavaScript — 在 HTML 中嵌入 JavaScript 代码的模板语言。
1.2 为什么需要模板
问题:res.send 中写大量 HTML 混乱不堪 → 违反关注点分离。
javascript
// ❌ 结构与逻辑混在一起
res.send(`
<html><head><link rel="stylesheet" href="..."></head>
<body><h1>Hello ${name}</h1><p>Welcome...</p></body></html>
`);
// ✅ 用 EJS 分离:服务器传数据,模板管展示
res.render("index.ejs", { name: "Hugo" });1.3 工作流程
1. 用户提交表单 → POST 请求到服务器
2. 服务器处理数据(req.body)
3. 服务器 res.render("template.ejs", { data })
4. EJS 将 data 注入模板 → 生成 HTML
5. 浏览器渲染最终 HTML二、安装与配置
bash
npm i ejsEJS 文件必须放在 views/ 目录下:
project/
├── index.js
├── package.json
├── public/ ← 静态文件
│ ├── styles/
│ └── images/
└── views/ ← EJS 模板(必须叫 views)
├── index.ejs
├── about.ejs
└── partials/
├── header.ejs
└── footer.ejsVS Code 插件:安装 "EJS Language Support"(Digital Brain Stem)以获得语法高亮。
三、EJS 标签语法
3.1 六种标签
| 标签 | 名称 | 功能 | 示例 |
|---|---|---|---|
<%= %> | 输出标签 | 执行 JS 并 输出结果 | <%= name %> → Angela |
<% %> | 代码标签 | 执行 JS 不输出 | <% if (true) { %> |
<%- %> | HTML 标签 | 输出 不转义的 HTML | <%- "<h1>Hi</h1>" %> → 渲染 H1 |
<%% %> | 转义标签 | 显示 EJS 标签本身 | <%%= %> → 显示 <%= %> |
<%# %> | 注释标签 | EJS 注释(不渲染) | <%# 这是注释 %> |
<%- include() %> | 包含标签 | 引入其他 EJS 文件 | <%- include("partials/header.ejs") %> |
3.2 输出标签 <%= %>
html
<!-- 变量输出 -->
<h1>Hello, <%= name %>!</h1>
<!-- 表达式输出 -->
<p>You have <%= items.length %> items.</p>3.3 代码标签 <% %>
每行 JS 代码 都需要独立的 EJS 标签:
html
<ul>
<% for (let i = 0; i < items.length; i++) { %>
<li><%= items[i] %></li>
<% } %>
</ul>forEach 写法:
html
<ul>
<% items.forEach(function(fruit) { %>
<li><%= fruit %></li>
<% }); %>
</ul>if-else:
html
<% if (seconds % 2 === 0) { %>
<ul>
<% items.forEach(function(item) { %>
<li><%= item %></li>
<% }); %>
</ul>
<% } else { %>
<p>No items to display.</p>
<% } %>常见错误
html
<!-- ❌ 不能用一个标签包裹所有 JS -->
<% if (condition) {
// HTML goes here
} %>
<!-- ✅ 每部分 JS 都需要独立标签 -->
<% if (condition) { %>
<p>HTML content</p>
<% } %>3.4 HTML 标签 <%- %>
html
<!-- 变量含 HTML 标记,会被渲染为实际 HTML -->
<%- "<strong>Bold Text</strong>" %>
<!-- 输出: Bold Text(加粗显示) -->
<!-- 对比:输出标签会转义 HTML -->
<%= "<strong>Bold Text</strong>" %>
<!-- 输出: <strong>Bold Text</strong>(原样显示) -->四、数据传递
4.1 服务器 → 模板(res.render)
javascript
app.get("/", (req, res) => {
res.render("index.ejs", {
title: "My Page", // 字符串
dayType: "weekday", // 字符串
items: ["Apple", "Banana"], // 数组
htmlContent: "<em>Hello</em>" // HTML
});
});在 EJS 中使用:
html
<h1><%= title %></h1>
<p>Today is a <%= dayType %></p>
<ul>
<% items.forEach(function(item) { %>
<li><%= item %></li>
<% }); %>
</ul>
<%- htmlContent %>4.2 客户端 → 服务器(表单 POST)
HTML 表单:
html
<form action="/submit" method="POST">
<label for="fName">First Name:</label>
<input type="text" name="fName" required>
<label for="lName">Last Name:</label>
<input type="text" name="lName" required>
<input type="submit" value="OK">
</form>服务器接收:
javascript
app.use(bodyParser.urlencoded({ extended: true }));
app.post("/submit", (req, res) => {
const firstName = req.body.fName; // name 属性对应
const lastName = req.body.lName;
const numberOfLetters = firstName.length + lastName.length;
res.render("index.ejs", { numberOfLetters: numberOfLetters });
});4.3 处理未定义变量 — locals
html
<!-- ❌ 如果变量不存在会崩溃 -->
<% if (numberOfLetters) { %>
<!-- ✅ 用 locals 安全检查 -->
<% if (locals.numberOfLetters) { %>
<h1>There are <%= numberOfLetters %> letters in your name.</h1>
<% } else { %>
<h1>Enter your name below 👇</h1>
<% } %>locals 的作用
locals 对象 始终存在,包含所有通过 res.render 传递的变量。
直接用变量名检查 if (numberOfLetters) 在 EJS 中会抛出 ReferenceError,但 locals.numberOfLetters 会安全返回 undefined。
五、静态文件
5.1 配置 public 目录
javascript
// 告诉 Express 静态文件在 public/ 目录下
app.use(express.static("public"));5.2 在 EJS 中引用
html
<!-- CSS:路径相对于 public/ -->
<link rel="stylesheet" href="/styles/layout.css">
<!-- 图片 -->
<img src="/images/logo.png">
<!-- JS -->
<script src="/scripts/main.js"></script>5.3 动态文件 vs 静态文件
| 类型 | 位置 | 特点 | 示例 |
|---|---|---|---|
| 动态 | views/ | 由服务器动态生成 | .ejs 模板 |
| 静态 | public/ | 不变,直接提供 | CSS、图片、前端 JS |
六、Partials(局部模板)
6.1 概念
Partials = 可复用的 EJS 代码片段(如 header、footer、navbar)。
目的:避免在每个页面重复相同的代码。
6.2 include 语法
html
<!-- 文件路径相对于 views/ 目录 -->
<%- include("partials/header.ejs") %>
<h1>Page Content</h1>
<p>This is the unique part of each page.</p>
<%- include("partials/footer.ejs") %>6.3 项目结构
views/
├── index.ejs ← include header + footer
├── about.ejs ← include header + footer
├── contact.ejs ← include header + footer
└── partials/
├── header.ejs ← <html><head>... + nav bar
└── footer.ejs ← footer + </body></html>header.ejs 示例:
html
<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
<link rel="stylesheet" href="/styles/layout.css">
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>footer.ejs 示例:
html
<footer>
<p>Copyright © 2026</p>
</footer>
</body>
</html>6.4 多页面路由
javascript
app.get("/", (req, res) => {
res.render("index.ejs");
});
app.get("/about", (req, res) => {
res.render("about.ejs");
});
app.get("/contact", (req, res) => {
res.render("contact.ejs");
});💡 HTML 中的
<a href="/about">等链接就是在发送 GET 请求 到对应路由。
七、完整项目:日期建议网站
7.1 服务器端(index.js)
javascript
import express from "express";
const app = express();
const port = 3000;
app.get("/", (req, res) => {
const today = new Date();
const day = today.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
let type = "a weekday";
let adv = "it's time to work hard";
if (day === 0 || day === 6) {
type = "the weekend";
adv = "it's time to have some fun";
}
res.render("index.ejs", { dayType: type, advice: adv });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});7.2 模板端(views/index.ejs)
html
<!DOCTYPE html>
<html>
<head><title>Weekday Warrior</title></head>
<body>
<h1>
Hey, it's <%= dayType %>,
<%= advice %>!
</h1>
</body>
</html>八、EJS 速查表
| 操作 | 代码 |
|---|---|
| 输出变量 | <%= variable %> |
| 执行代码(无输出) | <% code %> |
| 输出 HTML(不转义) | <%- htmlVariable %> |
| 注释 | <%# comment %> |
| 引入 partial | <%- include("path/file.ejs") %> |
| 安全检查变量 | <% if (locals.varName) { %> |
| 渲染模板 | res.render("file.ejs", { key: value }) |
| 静态文件 | app.use(express.static("public")) |