Node.js特训专栏-实战进阶:7.Express模板引擎选型与使用
🔥 欢迎来到 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')// 头部区域headmeta(charset='UTF-8')// 动态标题title #{pageTitle} | 我的网站// 条件判断if stylesheetlink(rel="stylesheet" href=stylesheet)// 正文内容body// 包含其他模板include ./header.pug// 主内容区main.containerh1.main-title 用户管理系统// 循环输出用户列表ul.user-listeach user, index in usersli.user-item(class=user.isAdmin ? 'admin' : '')span= index + 1 + '.'a(href="/users/"+user.id)= user.nameif user.isAdminspan.badge 管理员// 页脚footerp 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 实战干货持续更新,别错过提升开发技能的好机会~有任何问题或想了解的内容,也欢迎在评论区留言!👍🏻 👍🏻