【RuoYi-Eggjs】:基于 Handlebars 模板引擎实现服务端渲染

简介: 【RuoYi-Eggjs】基于 Handlebars 模板引擎实现服务端渲染,专为 Egg.js 打造。支持热更新、预编译、Partials 复用,内置 xif、math 等实用 Helper,轻松实现条件判断与运算。开箱即用,提升开发效率,适用于后台系统、静态页面等多场景,是构建 SSR 应用的优质选择。(238 字)

【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

充分利用 xifmath 简化模板逻辑:

<!-- ✅ 推荐:使用 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 都是一个不错的选择!


目录
相关文章
|
SQL 前端开发 数据可视化
MySQL Workbench 使用教程 - 如何使用 Workbench 操作 MySQL / MariaDB 数据库中文指南
MySQL Workbench 是一款专门为 MySQL 设计的可视化数据库管理软件,我们可以在自己的计算机上,使用图形化界面远程管理 MySQL 数据库。有关 MySQL 远程管理软件,你可以选择 Windows 下的 HeidiSQL,MacOS 下的 Sequel Ace 或者 MySQL 官方推出的跨平台客户端 MySQL Workbench 。
12525 0
|
5月前
|
机器学习/深度学习 资源调度 安全
【滤波跟踪】卡尔曼滤波电池SOC估计无迹(UEKF)+EKF扩展+安时法对比仿真
✅作者简介:热爱科研的Matlab仿真开发者,擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 🍎 往期回顾关注个人主页: Matlab科研工作室 🍊个人信条:格物致知,完整Matlab代码及仿真咨询内容私信。 🔥  内容介绍 一、电池 SOC 估计的重要性与挑战 电池荷电状态(SOC)反映了电池剩余电量,对于电动汽车、储能系统等应用至关重要。精确估计 SOC 有助于优化电池使用策略、保障系统安全运行并延长电池寿命。然而,电池的复杂电化学特性使得 SOC 估计颇具挑战。电池的内阻、容量等参数会随温度、充放电倍率及使用时间变化,且电池的充放电过程涉及复杂的电化学反应,呈
274 1
|
8月前
Wireshark_win32_2.2.1.0安装步骤详解
下载Wireshark安装包并双击运行,按提示选择语言、同意协议,确保勾选WinPcap驱动。可自定义安装路径和快捷方式,安装完成后建议重启电脑。启动时以管理员身份运行,即可开始抓包分析网络流量。(239字)
|
人工智能 JavaScript Java
函数式接口
本文介绍了Java中的函数式接口及其应用。函数式接口是指仅包含一个抽象方法的接口,可配合Lambda表达式简化代码。主要内容包括:1)函数式接口的概念与定义;2)函数式接口的使用场景,如作为方法参数、延迟执行(lambda优化日志案例)、作为返回值等;3)常用函数式接口详解,如Supplier(生产数据)、Consumer(消费数据)、Predicate(条件判断)、Function(类型转换)。每部分通过具体示例和代码解读,展示了函数式接口在实际开发中的灵活应用。
378 0
【Java集合类面试十一】、HashMap为什么用红黑树而不用B树?
HashMap选择使用红黑树而非B树,是因为红黑树在内存中实现简单,节点更小,占用内存少,且在插入、删除和查找操作上提供更好的平衡性能。
|
前端开发 网络协议 Go
为什么ChatGPT选择了SSE,而不是WebSocket?
为什么ChatGPT选择了SSE,而不是WebSocket?
1437 2
|
算法 调度
【调度算法】NSGA II
【调度算法】NSGA II
2205 1
|
设计模式 Java Kotlin
Kotlin 中的 run 函数详解
Kotlin 中的 run 函数详解
643 0
|
缓存 安全 网络协议
http和https的区别是什么?
http和https的区别是什么?

热门文章

最新文章