【RuoYi-Eggjs】:基于 Handlebars 模板引擎实现服务端渲染
前言
在 Web 开发中,选择合适的模板引擎能让前端开发事半功倍。Handlebars 作为一款轻量级的语义化模板引擎,以其简洁的语法和强大的功能深受开发者喜爱。[ruoyi-eggjs-handlebars](https://github.com/undsky/ruoyi-eggjs-handlebars) 就是为 Egg.js 框架量身定制的 Handlebars 插件,让你在 Egg.js 中也能享受 Handlebars 的便利。
为什么选择 Handlebars?
📝 语法简洁直观
Handlebars 使用双花括号 {{}} 作为标记,语法清晰易懂:
<h1>Hello {{name}}!</h1>
<p>年龄:{{age}} 岁</p>
🔒 逻辑分离
Handlebars 强调"无逻辑"模板(logic-less),将业务逻辑与视图层严格分离,模板只负责展示数据,逻辑处理在 Controller 或 Service 层完成。这种设计让代码结构更清晰、更易维护。
🎯 功能强大
虽然是"无逻辑"模板,但 Handlebars 通过 Helper 机制提供了丰富的功能扩展,既保持了模板的简洁性,又满足了实际开发的需求。
核心特性
🚀 性能优化
插件针对不同环境做了优化:
- 开发环境:每次渲染时重新加载模板,支持热更新
- 生产环境:启动时预编译所有模板,缓存在内存中,提升渲染性能
🧩 Partials 支持
Partials(代码片段)功能让你可以复用页面的公共部分,比如头部、尾部、侧边栏等:
<!-- app/view/partials/header.html -->
<header>
<h1>{{title}}</h1>
<nav>...</nav>
</header>
<!-- 在其他模板中使用 -->
{{> header}}
🛠️ 内置实用 Helper
插件内置了几个常用的 Helper,开箱即用:
1. xif - 强化的条件判断
支持复杂表达式,比原生的 {{#if}} 更强大:
{{#xif "age > 18"}}
<p>成年人</p>
{{else}}
<p>未成年</p>
{{/xif}}
{{#xif "score >= 60 && score < 80"}}
<p>及格</p>
{{/xif}}
2. math - 数学运算
在模板中直接进行数学计算:
<p>原价:{{price}}</p>
<p>折后价:{{math price "*" 0.8}}</p>
<p>库存剩余:{{math total "-" sold}}</p>
3. link - CSS 引入
快速引入样式文件,自动处理时间戳:
{{link "/static/css/style.css"}}
<!-- 输出: <link rel="stylesheet" href="/static/css/style.css?ts=1234567890"> -->
4. script - JS 引入
快速引入脚本文件,同样支持时间戳:
{{script "/static/js/app.js"}}
<!-- 输出: <script src="/static/js/app.js?ts=1234567890"></script> -->
时间戳功能:默认开启,可有效避免浏览器缓存导致的静态资源更新问题。
快速上手
安装
npm i ruoyi-eggjs-handlebars --save
配置
1. 启用插件
// config/plugin.js
exports.handlebars = {
enable: true,
package: "ruoyi-eggjs-handlebars",
};
2. 基础配置
// config/config.default.js
config.handlebars = {
options: {
data: true,
compat: true,
noEscape: false, // 是否转义 HTML
strict: false, // 严格模式
preventIndent: true, // 防止自动缩进
},
runtimeOptions: {
allowProtoMethodsByDefault: true,
allowProtoPropertiesByDefault: true,
},
partialsPath: null, // Partials 目录,默认为 view.root/partials
ts: true, // 静态资源是否添加时间戳
};
使用示例
Controller 层
// app/controller/home.js
class HomeController extends Controller {
async index() {
const { ctx } = this;
await ctx.render('index.html', {
title: '欢迎使用 Handlebars',
user: {
name: '张三',
age: 25,
},
products: [
{ name: '商品A', price: 99 },
{ name: '商品B', price: 199 },
],
});
}
}
模板文件
<!-- app/view/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
{{link "/static/css/style.css"}}
</head>
<body>
{{> header}}
<main>
<h2>用户信息</h2>
<p>姓名:{{user.name}}</p>
<p>年龄:{{user.age}}</p>
{{#xif "user.age >= 18"}}
<p class="badge">成年用户</p>
{{/xif}}
<h2>商品列表</h2>
<ul>
{{#each products}}
<li>
{{this.name}} -
<span class="price">¥{{this.price}}</span>
<span class="discount">折后价:¥{{math this.price "*" 0.8}}</span>
</li>
{{/each}}
</ul>
</main>
{{> footer}}
{{script "/static/js/app.js"}}
</body>
</html>
实战场景
场景 1:后台管理系统布局
后台系统通常有固定的布局结构,使用 Partials 可以很好地复用:
app/view/
├── partials/
│ ├── header.html # 顶部导航
│ ├── sidebar.html # 侧边栏
│ └── footer.html # 底部
├── layout.html # 布局模板
├── user/
│ ├── list.html # 用户列表
│ └── detail.html # 用户详情
└── dashboard.html # 仪表盘
布局模板
<!-- app/view/layout.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{title}} - 管理后台</title>
{{link "/static/css/admin.css"}}
</head>
<body>
{{> header}}
<div class="container">
{{> sidebar}}
<main class="content">
{{{body}}} <!-- 三个花括号表示不转义 HTML -->
</main>
</div>
{{> footer}}
{{script "/static/js/admin.js"}}
</body>
</html>
场景 2:数据列表渲染
使用 Handlebars 的 {{#each}} 和内置 Helper 轻松实现数据列表:
<!-- app/view/user/list.html -->
<div class="user-list">
<h2>用户列表(共 {{users.length}} 人)</h2>
<table>
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{{#each users}}
<tr>
<td>{{math @index "+" 1}}</td>
<td>{{this.name}}</td>
<td>{{this.age}}</td>
<td>
{{#xif "this.age >= 18"}}
<span class="badge success">成年</span>
{{else}}
<span class="badge warning">未成年</span>
{{/xif}}
</td>
<td>
<a href="/user/{{this.id}}">查看</a>
<a href="/user/{{this.id}}/edit">编辑</a>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
场景 3:条件渲染
使用 xif Helper 实现复杂的条件判断:
<!-- app/view/product/detail.html -->
<div class="product-detail">
<h1>{{product.name}}</h1>
<p class="price">¥{{product.price}}</p>
<!-- 库存状态显示 -->
{{#xif "product.stock > 10"}}
<span class="stock success">库存充足</span>
{{else}}
{{#xif "product.stock > 0"}}
<span class="stock warning">仅剩 {{product.stock}} 件</span>
{{else}}
<span class="stock danger">已售罄</span>
{{/xif}}
{{/xif}}
<!-- 折扣信息 -->
{{#xif "product.discount < 1"}}
<div class="discount-info">
<p>原价:¥{{product.price}}</p>
<p>折扣价:¥{{math product.price "*" product.discount}}</p>
<p>节省:¥{{math product.price "-" (math product.price "*" product.discount)}}</p>
</div>
{{/xif}}
</div>
最佳实践
1. 合理组织 Partials
将公共部分抽取为 Partials,提高代码复用性:
partials/
├── common/
│ ├── header.html # 页头
│ ├── footer.html # 页脚
│ └── meta.html # Meta 标签
├── components/
│ ├── pagination.html # 分页组件
│ ├── breadcrumb.html # 面包屑
│ └── alert.html # 提示框
└── forms/
├── input.html # 输入框
└── select.html # 下拉框
2. 善用内置 Helper
充分利用 xif 和 math 简化模板逻辑:
<!-- ✅ 推荐:使用 xif 进行复杂判断 -->
{{#xif "score >= 90"}}
<span class="grade">优秀</span>
{{else}}
{{#xif "score >= 60"}}
<span class="grade">及格</span>
{{else}}
<span class="grade">不及格</span>
{{/xif}}
{{/xif}}
<!-- ❌ 不推荐:在 Controller 中预处理 -->
<!-- 这会增加 Controller 的复杂度 -->
3. 静态资源管理
合理使用时间戳功能,避免缓存问题:
// config/config.default.js
config.handlebars = {
ts: process.env.NODE_ENV === 'production', // 生产环境启用
};
4. 避免过度嵌套
虽然 Handlebars 支持嵌套,但过度嵌套会降低可读性:
<!-- ✅ 推荐:扁平化结构 -->
{{#each users}}
{{> userCard}}
{{/each}}
<!-- ❌ 不推荐:过度嵌套 -->
{{#each categories}}
{{#each this.products}}
{{#each this.variants}}
...
{{/each}}
{{/each}}
{{/each}}
配置参数详解
编译选项(options)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| data | Boolean | true | 是否启用 @data 跟踪 |
| compat | Boolean | true | 允许递归查找 |
| noEscape | Boolean | false | 是否禁用 HTML 转义 |
| strict | Boolean | false | 严格模式(缺失参数时抛异常) |
| preventIndent | Boolean | true | 防止自动缩进 |
| ignoreStandalone | Boolean | true | 不移除单独的标签 |
运行时选项(runtimeOptions)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| allowProtoMethodsByDefault | Boolean | true | 允许访问原型方法 |
| allowProtoPropertiesByDefault | Boolean | true | 允许访问原型属性 |
插件选项
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| partialsPath | String/Array | null | Partials 目录路径 |
| ts | Boolean | true | 静态资源是否添加时间戳 |
总结
ruoyi-eggjs-handlebars 是一个功能完善、性能优异的 Egg.js 模板引擎插件,它的优势在于:
- 简单易用:语法简洁,学习成本低
- 性能优秀:生产环境预编译,开发环境热更新
- 功能实用:内置 Helper 满足常见需求
- 代码复用:Partials 机制支持组件化开发
- 灵活配置:丰富的配置项,适应不同场景
如果你正在使用 Egg.js 开发传统的服务端渲染应用,或者需要一个轻量级的模板引擎来生成 HTML 邮件、导出报表等,ruoyi-eggjs-handlebars 都是一个不错的选择!
- 插件地址:ruoyi-eggjs-handlebars
- 项目地址:RuoYi-Eggjs
- 开发文档:RuoYi-Eggjs 文档
6万+

被折叠的 条评论
为什么被折叠?



