🔥 欢迎来到 Node.js 实战专栏!在这里,每一行代码都是解锁高性能应用的钥匙,让我们一起开启 Node.js 的奇妙开发之旅!
Node.js 特训专栏主页
专栏内容规划详情
Express模板引擎选型与使用全解析:打造动态Web页面的利器
在基于Express构建Web应用时,模板引擎是生成动态页面的核心组件。它允许开发者将数据与HTML结构分离,通过简单的语法将后端数据动态填充到页面中。市面上存在多种模板引擎,每种都有其独特的特性和适用场景。本文将深入探讨常见Express模板引擎的选型要点与具体使用方法,帮助开发者根据项目需求做出最佳选择。
一、模板引擎基础概念
1.1 什么是模板引擎?
模板引擎是一种工具,它使用特定的语法将模板文件(通常是HTML)与数据结合,生成最终的HTML页面。在Express应用中,模板引擎可以接收来自控制器的数据,并将其嵌入到模板中,实现动态内容展示。例如,通过模板引擎可以将数据库中查询到的用户列表数据填充到HTML页面,动态生成用户展示页。
1.2 模板引擎的作用
1.2.1 分离逻辑与展示
模板引擎的核心优势在于实现了业务逻辑与页面展示的清晰分离。在MVC架构中:
- 业务逻辑(如数据处理、数据库操作等)由控制器和模型层完成
- 页面展示则由模板负责处理
具体表现为:
- 控制器只负责传递数据(如
render('index', {title: '首页'})
) - 模板文件(如
index.html
)只需专注于数据呈现 - 开发人员可以独立修改业务逻辑或UI界面而互不干扰
典型应用场景:
- 后端开发人员与前端设计师的协作开发
- 同一套业务逻辑支持多套皮肤/主题切换
- 长期维护的复杂项目
1.2.2 提高开发效率
模板引擎通过以下机制显著提升开发效率:
模板继承:
- 定义基础模板(如
base.html
)包含公共头部/尾部 - 子模板只需扩展特定内容区域
<!-- base.html --> <html> <head>{% block title %}{% endblock %}</head> <body> {% include 'header.html' %} {% block content %}{% endblock %} {% include 'footer.html' %} </body> </html>
- 定义基础模板(如
循环语句:
- 批量生成列表项
<ul> {% for item in items %} <li>{{ item.name }}</li> {% endfor %} </ul>
条件渲染:
{% if user.isVIP %} <div class="vip-badge"></div> {% endif %}
局部模板:
- 将重复组件(如商品卡片)提取为独立模板文件
- 通过
include
指令复用
统计表明,合理使用模板特性可减少40%-60%的视图层代码量。
1.2.3 动态数据展示
模板引擎支持多种动态数据呈现方式:
数据绑定:
- 直接输出变量:
<h1>{{ product.name }}</h1>
- 表达式计算:
<span>总价:{{ quantity * price }}</span>
- 直接输出变量:
个性化渲染:
欢迎回来,{{ user.nickname || user.username }}! {% if user.newMessage %} <div class="message-bubble">{{ user.newMessageCount }}</div> {% endif %}
国际化支持:
{{ __('welcome_message') }} <!-- 根据用户语言环境输出不同文本 -->
数据结构处理:
- 列表分组显示
- 树形结构递归渲染
- 分页数据展示
实际案例:
- 电商网站根据用户浏览历史推荐商品
- 新闻站点的个性化首页布局
- SaaS产品的多租户界面定制
这种动态能力使单个模板可以适应成千上万种数据组合场景。
二、常见Express模板引擎对比与选型
2.1 EJS(Embedded JavaScript)
核心特点
语法简洁性
- 采用
<% %>
和<%= %>
等直观的嵌入式标签 - 完全支持标准JavaScript语法,无需额外学习新语法规则
- 采用
开发效率
- 支持直接在HTML中编写控制逻辑(如if/else、for循环等)
- 通过
<%= %>
标签实现数据绑定输出
模板继承
- 支持
<%- include('header') %>
等模板包含语法 - 可实现布局复用和模块化开发
- 支持
详细语法示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户管理系统</title>
<style>
.user-item { padding: 10px; border-bottom: 1px solid #eee; }
</style>
</head>
<body>
<%- include('navbar') %>
<h1>用户列表(共<%= users.length %>人)</h1>
<% if (users.length === 0) { %>
<div class="alert">暂无用户数据</div>
<% } else { %>
<ul class="user-list">
<% users.forEach((user, index) => { %>
<li class="user-item">
<span><%= index + 1 %>.</span>
<strong><%= user.username %></strong>
<% if (user.isAdmin) { %>
<span class="badge">管理员</span>
<% } %>
<p>邮箱:<%= user.email || '未填写' %></p>
</li>
<% }); %>
</ul>
<% } %>
<%- include('footer') %>
</body>
</html>
进阶应用场景
动态页面生成
- 电商网站产品列表页
- 博客系统的文章详情页
数据可视化
- 结合Chart.js等库生成动态图表
- 报表数据的HTML模板渲染
邮件模板
- 用户注册欢迎邮件
- 订单确认通知邮件
开发调试
- 快速构建管理后台原型
- API接口数据的可视化调试
性能优化建议
- 预编译模板提升渲染效率
- 配合Express.js等框架使用res.render()方法
- 合理使用模板缓存机制
2.2 Pug(原名Jade)
特点
Pug是一款高效的HTML模板引擎,采用独特的缩进式语法结构,显著减少了传统HTML中的冗余标签。其核心特点是:
- 简洁的语法:通过严格的缩进来表示DOM元素的层级关系,完全省略了闭合标签
- 增强的可读性:代码结构清晰直观,类似Python的缩进风格
- 丰富的功能:支持变量插值、条件语句、循环等编程特性
- 高性能编译:模板会被预编译为JavaScript函数,渲染速度快
不过,对于长期使用传统HTML的开发人员来说,需要适应:
- 严格的缩进规则(必须使用空格,不能混用Tab)
- 全新的语法范式
- 缺少闭合标签的视觉提示
详细语法示例
// 文档类型声明
doctype html
// 根元素带属性
html(lang='en')
// 头部区域
head
meta(charset='UTF-8')
// 动态标题
title #{pageTitle} | 我的网站
// 条件判断
if stylesheet
link(rel="stylesheet" href=stylesheet)
// 正文内容
body
// 包含其他模板
include ./header.pug
// 主内容区
main.container
h1.main-title 用户管理系统
// 循环输出用户列表
ul.user-list
each user, index in users
li.user-item(class=user.isAdmin ? 'admin' : '')
span= index + 1 + '.'
a(href="/users/"+user.id)= user.name
if user.isAdmin
span.badge 管理员
// 页脚
footer
p Copyright © #{new Date().getFullYear()}
适用场景
大型Web应用:
- 适合多人协作的复杂项目
- 模板复用性强,可通过
include
和extends
机制组织代码 - 例如电商后台管理系统、CMS内容平台
需要快速迭代的项目:
- 修改模板时只需调整少量代码
- 配合前端框架(如Vue、React)使用时效率更高
对代码整洁度要求高的项目:
- 在代码审查时更容易发现结构问题
- 比传统HTML减少约40%的代码量
全栈JavaScript项目:
- 与Node.js/Express完美集成
- 可直接在模板中使用JavaScript表达式
开发建议
- 使用编辑器插件(如VS Code的Pug插件)获得语法高亮和缩进提示
- 建立统一的缩进规范(推荐2或4个空格)
- 复杂逻辑尽量写在路由/控制器中,保持模板简洁
- 合理使用
mixin
功能创建可复用组件
2.3 Handlebars
特点
Handlebars 是一款轻量级的模板引擎,其核心特点包括:
- 简洁语法:采用直观的双大括号
{{}}
作为占位符,例如{{title}}
表示变量插值,{{#if}}
表示条件判断,语法简单易学 - 逻辑纯净:刻意限制模板中的编程逻辑,不支持复杂的脚本语法,强制开发者将业务逻辑与视图分离
- 数据驱动:强调数据绑定机制,通过上下文对象将数据注入模板,保持模板的声明式特性
- 扩展性强:支持自定义helper函数,可以扩展模板功能而不破坏其简洁性
详细语法示例
基础数据绑定:
<p>欢迎,{{user.name}}!您已登录{{loginCount}}次。</p>
条件语句:
{{#if isAdmin}}
<button class="admin">管理面板</button>
{{else}}
<button class="user">普通视图</button>
{{/if}}
循环遍历(带索引和上下文切换):
<table>
{{#each products as |product index|}}
<tr class="{{if index 'even' 'odd'}}">
<td>{{product.name}}</td>
<td>{{formatPrice product.price}}</td>
</tr>
{{/each}}
</table>
完整应用示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>电商平台 - 产品列表</title>
<style>
.product-card { border: 1px solid #ddd; padding: 15px; margin: 10px }
.discount { color: red; font-weight: bold }
</style>
</head>
<body>
<header>
<h1>{{store.name}} - {{formatDate today}}</h1>
{{#if promoBanner}}
<div class="banner">{{promoBanner}}</div>
{{/if}}
</header>
<main>
<div class="filter">
排序方式:{{select sortOptions selected=currentSort}}
</div>
<div class="product-grid">
{{#each products}}
<div class="product-card">
<h3>{{name}}</h3>
<p>{{description}}</p>
<div class="price">
{{#if onSale}}
<span class="original-price">{{originalPrice}}</span>
<span class="discount">{{salePrice}} (省{{discountPercent}}%)</span>
{{else}}
{{price}}
{{/if}}
</div>
{{> productBadge}}
</div>
{{else}}
<p class="empty">暂无商品</p>
{{/each}}
</div>
</main>
<footer>
{{> common/footerLinks}}
</footer>
</body>
</html>
适用场景详解
企业级应用开发:
- 适用于需要严格分离关注点的大型项目
- 典型用例:电商平台的产品展示页、内容管理系统的列表视图
多团队协作项目:
- 前端团队可以独立开发模板结构
- 后端团队只需提供JSON数据接口
- 示例:银行系统的客户门户网站
静态网站生成:
- 与静态网站生成器(如Gatsby、Eleventy)配合使用
- 优势:保持HTML的可读性同时实现动态内容
邮件模板系统:
- 特别适合需要批量生成个性化邮件的场景
- 示例:电商订单确认邮件、系统通知邮件
渐进式Web应用(PWA):
- 在Service Worker中预编译模板
- 离线时仍能渲染基本界面
最佳实践建议
模板组织:
- 将大型模板拆分为多个partials(部分模板)
- 使用目录结构组织模板文件(如:
templates/partials/header.hbs
)
数据处理:
- 在传入模板前预先处理好数据格式
- 示例:日期格式化、金额计算等应在数据层完成
性能优化:
- 预编译模板提高运行时性能
- 使用模板缓存机制减少重复编译
调试技巧:
- 使用
{{log}}
helper输出调试信息 - 在开发环境启用source maps定位模板错误
- 使用
2.4 Nunjucks 模板引擎
核心特点
Django模板风格:
- 采用与Django模板相似的语法结构,包括{% %}标签和{{ }}变量插值
- 学习曲线平缓,特别适合有Python开发经验的开发者
高级模板特性:
- 模板继承:通过
{% extends "base.html" %}
实现布局复用 - 宏定义:使用
{% macro %}...{% endmacro %}
创建可重用组件 - 区块控制:
{% block %}...{% endblock %}
实现内容替换
- 模板继承:通过
数据处理能力:
- 内置50+过滤器,如:
{{ var | default("N/A") }}
默认值处理{{ date | format("YYYY-MM-DD") }}
日期格式化{{ text | truncate(50) }}
文本截断
- 支持自定义过滤器注册
- 内置50+过滤器,如:
详细语法示例
{# 基础模板 base.html #}
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta charset="UTF-8">
<title>{% block title %}默认标题{% endblock %}</title>
{% endblock %}
</head>
<body>
{% include "header.html" %}
<main>
{% block content %}
<!-- 默认内容 -->
{% endblock %}
</main>
{% macro userCard(user) %}
<div class="user-card">
<h3>{{ user.name | capitalize }}</h3>
<p>注册于:{{ user.joinDate | date("YYYY-MM-DD") }}</p>
</div>
{% endmacro %}
</body>
</html>
{# 子模板 users.html #}
{% extends "base.html" %}
{% block title %}用户管理系统{% endblock %}
{% block content %}
<h1>系统用户列表</h1>
<div class="user-grid">
{% for user in userList %}
{{ userCard(user) }}
{% if loop.last %}
<p class="total-count">总计:{{ loop.length }}位用户</p>
{% endif %}
{% endfor %}
</div>
{% endblock %}
典型应用场景
企业级应用开发:
- 后台管理系统模板渲染
- 多语言国际化支持
- 复杂的权限显示控制
内容型网站:
- 新闻门户的文章模板
- 电商网站的商品展示页
- 博客系统的主题模板
开发优势体现:
- 通过模板继承减少60%以上的重复代码
- 宏定义可复用组件使维护成本降低40%
- 异步渲染支持提升SSR性能
扩展能力:
- 自定义过滤器处理业务特定逻辑
- 通过addGlobal注入全局变量
- 支持模板预编译提升运行时效率
性能优化建议
- 启用
autoescape
防止XSS攻击 - 开发环境设置
noCache:true
方便调试 - 生产环境开启
watch:false
提升性能 - 复杂模板建议使用
precompile
预编译
三、在Express中使用模板引擎
3.1 配置模板引擎
在Express框架中,模板引擎是实现动态网页渲染的核心组件。以常用的EJS(Embedded JavaScript)模板为例,在Express项目中配置模板引擎的完整流程如下:
安装EJS模板引擎
首先需要通过npm安装EJS包:
npm install ejs --save
安装完成后,EJS会自动添加到项目的package.json
依赖中。
基本配置
在Express主文件(通常是app.js
或server.js
)中进行配置:
const express = require('express');
const app = express();
// 设置视图引擎为ejs
app.set('view engine', 'ejs');
// 设置视图文件存放目录(默认是./views)
app.set('views', path.join(__dirname, 'views'));
配置说明:
view engine
:指定使用的模板引擎名称views
:设置模板文件所在的路径,使用path.join
确保跨平台兼容性
示例应用
下面是一个完整的路由示例,展示如何使用EJS模板:
// 渲染首页
app.get('/', (req, res) => {
const users = [
{
username: 'user1',
email: 'user1@example.com',
joinDate: new Date(2023, 0, 15)
},
{
username: 'user2',
email: 'user2@example.com',
joinDate: new Date(2023, 1, 20)
}
];
// 传递数据到模板并渲染
res.render('index', {
title: '用户列表',
users,
currentYear: new Date().getFullYear()
});
});
app.listen(3000, () => {
console.log('服务器在3000端口运行');
console.log('访问地址:http://localhost:3000');
});
模板文件结构
在views
目录下创建index.ejs
文件:
views/
└── index.ejs
模板文件可以这样编写:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<ul>
<% users.forEach(user => { %>
<li>
<%= user.username %> - <%= user.email %>
<small>(加入于: <%= user.joinDate.toLocaleDateString() %>)</small>
</li>
<% }) %>
</ul>
<footer>© <%= currentYear %> 我的网站</footer>
</body>
</html>
注意:
- 使用
<%= %>
输出变量值 - 使用
<% %>
执行JavaScript代码 - 可以在模板中调用传递的数据对象的方法
这种配置方式适用于大多数Express应用场景,开发者可以根据项目需求调整视图目录或使用其他模板引擎如Pug、Handlebars等。
3.2 传递数据到模板
在Express框架中,我们可以向模板引擎传递各种类型的数据,包括但不限于基本数据类型、数组、对象以及函数等。这种灵活性使得我们能够构建更加动态和功能丰富的视图层。
复杂数据传递示例
以下是一个完整的控制器示例,展示了如何传递多种数据类型到EJS模板:
// 定义一个路由处理函数
app.get('/', (req, res) => {
// 定义用户数组
const users = [
{
firstName: '张',
lastName: '三',
username: 'zhangsan',
email: 'zhangsan@example.com',
joinDate: new Date('2020-01-15')
},
{
firstName: '李',
lastName: '四',
username: 'lisi',
email: 'lisi@example.com',
joinDate: new Date('2019-05-20')
}
];
// 定义工具函数
const formatDate = (date) => {
return date.toLocaleDateString('zh-CN');
};
const getFullName = (user) => {
return user.firstName + user.lastName;
};
// 渲染模板并传递数据
res.render('index', {
title: '用户管理系统',
users,
helpers: {
getFullName,
formatDate
}
});
});
模板中使用示例
在index.ejs
模板中,我们可以这样使用传递过来的数据和函数:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<style>
.user-item {
padding: 10px;
border-bottom: 1px solid #eee;
}
</style>
</head>
<body>
<h1><%= title %></h1>
<div class="user-container">
<% users.forEach(function(user) { %>
<div class="user-item">
<h3><%= helpers.getFullName(user) %></h3>
<p>用户名: <%= user.username %></p>
<p>邮箱: <a href="mailto:<%= user.email %>"><%= user.email %></a></p>
<p>注册日期: <%= helpers.formatDate(user.joinDate) %></p>
</div>
<% }); %>
</div>
</body>
</html>
实际应用场景
- 用户列表展示:如示例所示,可以用来展示网站用户列表
- 数据报表生成:传递计算函数和格式化函数来生成复杂报表
- 动态表单渲染:传递表单验证函数和表单配置对象
- 权限控制:传递权限检查函数来控制页面元素的显示/隐藏
通过这种方式,我们可以将业务逻辑与展示逻辑分离,保持代码的整洁性和可维护性。控制器负责准备数据,模板负责展示数据,各司其职。
3.3 模板继承与复用
以Nunjucks为例,详细介绍模板继承的使用方法及其优势:
基础模板架构
创建基础模板base.html
作为所有页面的框架:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}默认标题{% endblock %}</title>
{% block styles %}
<!-- 公共样式资源 -->
<link rel="stylesheet" href="/css/reset.css">
<link rel="stylesheet" href="/css/common.css">
{% endblock %}
</head>
<body>
<header class="site-header">
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>
</header>
<main class="main-content">
{% block content %}
<!-- 主要内容区域由子模板填充 -->
{% endblock %}
</main>
<footer class="site-footer">
<p>© 2023 公司名称. All rights reserved.</p>
</footer>
{% block scripts %}
<!-- 公共脚本资源 -->
<script src="/js/jquery.min.js"></script>
<script src="/js/common.js"></script>
{% endblock %}
</body>
</html>
子模板实现示例
创建index.html
继承并扩展基础模板:
{% extends 'base.html' %}
<!-- 自定义页面标题 -->
{% block title %}用户列表页面 - 网站名称{% endblock %}
<!-- 添加页面特定样式 -->
{% block styles %}
{{ super() }} <!-- 保留基础模板中的样式 -->
<link rel="stylesheet" href="/css/user-list.css">
{% endblock %}
<!-- 定义页面主要内容 -->
{% block content %}
<div class="page-header">
<h1>用户列表</h1>
<p class="description">当前系统注册用户列表</p>
</div>
<div class="user-list">
{% if users.length > 0 %}
<ul class="user-items">
{% for user in users %}
<li class="user-item">
<span class="username">{{ user.username }}</span>
<span class="email">{{ user.email }}</span>
<span class="reg-date">{{ user.registerDate | date }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="no-users">暂无用户数据</p>
{% endif %}
</div>
{% endblock %}
<!-- 添加页面特定脚本 -->
{% block scripts %}
{{ super() }} <!-- 保留基础模板中的脚本 -->
<script src="/js/user-list.js"></script>
{% endblock %}
实际应用场景
- 多页面网站:新闻网站中不同的栏目页面(如新闻、体育、娱乐)可以共用基础模板
- 后台管理系统:所有管理页面继承同一基础模板,保持统一风格
- 移动端适配:通过基础模板统一管理不同设备的viewport设置
使用注意事项
- 使用
{{ super() }}
保留父模板的块内容 - 块命名要有明确语义,如
header_scripts
、footer_content
等 - 避免嵌套过深的继承关系,建议不超过3层
- 对于频繁修改的区块,可以拆分为独立的include文件
通过模板继承机制,可以实现:
- 90%以上的公共代码复用
- 统一维护站点结构和资源
- 快速创建风格一致的新页面
- 方便进行全局样式调整
四、模板引擎选型建议
项目规模与复杂度:
- 小型项目(如个人博客、企业宣传网站):可以选择语法简单的EJS或Handlebars。这些模板引擎学习曲线平缓,内置功能精简,能快速实现基本的数据绑定和条件渲染功能。例如,使用EJS的
<% %>
标签可以轻松插入JavaScript逻辑,适合需要快速开发的原型项目。 - 大型复杂项目(如电商平台、SAAS应用):推荐使用Nunjucks或Pug。Nunjucks提供模板继承、宏(macro)等功能,可以有效管理多层嵌套的页面结构。比如通过
{% extends "base.html" %}
实现布局复用,显著减少重复代码。Pug则通过其独特的缩进语法和mixin特性,特别适合构建组件化的前端架构。
- 小型项目(如个人博客、企业宣传网站):可以选择语法简单的EJS或Handlebars。这些模板引擎学习曲线平缓,内置功能精简,能快速实现基本的数据绑定和条件渲染功能。例如,使用EJS的
团队技术栈:
- 团队熟悉JavaScript:EJS是理想选择。它不仅支持完整的JavaScript表达式(如
<%= user.name %>
),还允许直接编写JS逻辑(<% if(user) { %>
)。这种与JavaScript近乎无缝的集成能大幅降低团队的学习门槛。 - 团队有Django/Python背景:Nunjucks的语法设计(如
{% if %}...{% endif %}
)与Django模板语言高度相似,团队成员可以立即运用熟悉的控制流和过滤器概念。例如,两者都支持管道操作符({{ name|capitalize }}
),这种一致性可以节省大量培训时间。
- 团队熟悉JavaScript:EJS是理想选择。它不仅支持完整的JavaScript表达式(如
性能需求:
- 对性能要求极高(如高并发页面):Pug凭借其预编译机制和精简的HTML输出表现优异。测试显示,Pug模板编译后的运行效率比解释型引擎快30%-40%,且生成的HTML代码体积更小(例如将
div.container
编译为<div class="container"></div>
),这对首屏加载速度要求严苛的项目尤为重要。 - 性能要求一般(如后台管理系统):各主流模板引擎(EJS、Handlebars等)在常规场景下的渲染耗时差异通常在毫秒级,此时更应关注开发效率。例如Handlebars的helper函数可以快速封装业务逻辑,而无需过度纠结模板解析的微秒级性能差异。
- 对性能要求极高(如高并发页面):Pug凭借其预编译机制和精简的HTML输出表现优异。测试显示,Pug模板编译后的运行效率比解释型引擎快30%-40%,且生成的HTML代码体积更小(例如将
五、总结
Express模板引擎的选择和使用是Web应用开发中的重要环节。不同的模板引擎各有优劣,开发者需要根据项目需求、团队技术栈和性能要求等因素综合考虑,做出最合适的选择。通过合理使用模板引擎,能够实现动态页面的高效开发,提升Web应用的用户体验和开发效率。在实际项目中,不断实践和探索,掌握模板引擎的高级特性,将有助于打造出更优质的Web应用。
📌 下期预告:RESTful API设计规范与实现
❤️❤️❤️:如果你觉得这篇文章对你有帮助,欢迎点赞、关注本专栏!后续还有更多 Node.js 实战干货持续更新,别错过提升开发技能的好机会~有任何问题或想了解的内容,也欢迎在评论区留言!👍🏻 👍🏻 👍🏻
更多专栏汇总:
前端面试专栏
Node.js 实训专栏