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

【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)

参数类型默认值说明
dataBooleantrue是否启用 @data 跟踪
compatBooleantrue允许递归查找
noEscapeBooleanfalse是否禁用 HTML 转义
strictBooleanfalse严格模式(缺失参数时抛异常)
preventIndentBooleantrue防止自动缩进
ignoreStandaloneBooleantrue不移除单独的标签

运行时选项(runtimeOptions)

参数类型默认值说明
allowProtoMethodsByDefaultBooleantrue允许访问原型方法
allowProtoPropertiesByDefaultBooleantrue允许访问原型属性

插件选项

参数类型默认值说明
partialsPathString/ArraynullPartials 目录路径
tsBooleantrue静态资源是否添加时间戳

总结

ruoyi-eggjs-handlebars 是一个功能完善、性能优异的 Egg.js 模板引擎插件,它的优势在于:

  • 简单易用:语法简洁,学习成本低
  • 性能优秀:生产环境预编译,开发环境热更新
  • 功能实用:内置 Helper 满足常见需求
  • 代码复用:Partials 机制支持组件化开发
  • 灵活配置:丰富的配置项,适应不同场景

如果你正在使用 Egg.js 开发传统的服务端渲染应用,或者需要一个轻量级的模板引擎来生成 HTML 邮件、导出报表等,ruoyi-eggjs-handlebars 都是一个不错的选择!


<think>我们面对的是一个Maven循环依赖问题。根据错误信息,ruoyi-system模块依赖于ruoyi-common模块,而ruoyi-common模块又反过来依赖于ruoyi-system模块,形成了循环:ruoyi-system->ruoyi-common->ruoyi-system。解决循环依赖的常见方法:1.重构代码:将两个模块中相互依赖的部分提取到一个新的模块中,或者将公共代码下沉到基础模块。2.检查依赖关系:分析为什么ruoyi-common会依赖ruoyi-system?通常,common模块应该是被其他模块依赖的基础模块,不应该反过来依赖上层模块。具体步骤:1.查看ruoyi-common模块的pom.xml,确认它是否直接或间接地依赖了ruoyi-system。2.查看ruoyi-system模块的pom.xml,确认它对ruoyi-common的依赖。根据错误信息,循环路径是:ruoyi-system->ruoyi-common->ruoyi-system。因此,我们需要在ruoyi-common模块中移除对ruoyi-system的依赖。可能的情况:-ruoyi-common模块的pom.xml中,显式地引入了ruoyi-system依赖。这种情况应该删除该依赖。-或者,ruoyi-common模块中的某些类引用了ruoyi-system模块中的类,导致编译时需要ruoyi-system。这种情况需要重构代码,将ruoyi-common中依赖ruoyi-system的类移到ruoyi-system模块中,或者将两个模块中相互依赖的代码提取到第三个模块。操作建议:1.检查代码:在ruoyi-common模块中搜索对ruoyi-system模块中类的引用。如果存在,考虑将这些引用移除或重构。2.如果ruoyi-common确实需要一些原本在ruoyi-system中的功能,可以将这些功能提取到一个新的模块,然后让两个模块都依赖这个新模块。3.如果循环依赖是由于某些工具类或通用类被放置在ruoyi-system模块中,而这些类又被ruoyi-common需要,那么应该将这些类移动到ruoyi-common模块。注意:在Maven项目中,循环依赖是不允许的,因此必须打破这个循环。根据RuoYi项目的常见结构,ruoyi-common模块通常是基础工具类、通用枚举等,而ruoyi-system是系统模块,包含系统相关的功能。所以,ruoyi-common不应该依赖ruoyi-system。实际解决步骤:1.打开ruoyi-common模块的pom.xml,查看<dependencies>部分,如果发现ruoyi-system依赖,则将其删除。2.如果pom中没有直接依赖,则可能是传递依赖导致?但根据错误信息,是直接形成了循环,所以重点检查直接依赖。3.使用Maven命令分析依赖树:在项目根目录下运行`mvndependency:tree`,查看依赖树,定位循环依赖的位置。如果代码中存在类之间的循环依赖,那么需要修改代码。例如:-如果ruoyi-common中的类A使用了ruoyi-system中的类B,那么可以考虑:a.将类B移动到ruoyi-common中(如果类B是通用的,则合理)b.或者,将类A中依赖类B的部分抽离出来,通过接口解耦(依赖倒置),将接口放在ruoyi-common,而实现放在ruoyi-system,这样ruoyi-common只依赖接口,而实现ruoyi-system提供,但ruoyi-common不再直接依赖ruoyi-system。另一种方法是使用事件或消息机制来解耦两个模块之间的直接依赖。但是,由于这是一个常见问题,我们可以参考RuoYi项目的标准结构。实际上,在标准的RuoYi项目中,ruoyi-common模块是不应该依赖ruoyi-system模块的。因此,可能是开发过程中不小心引入了反向依赖。总结:先检查pom文件,再检查代码引用。优先确保ruoyi-common的pom中没有引入ruoyi-system依赖。然后检查代码,将ruoyi-common中引用ruoyi-system的类进行调整,要么移动类的位置,要么重构代码消除依赖。如果问题仍然存在,请提供更多信息,例如两个模块中相互引用的具体类名和代码片段,以便更精确地解决问题。</think>### 解决Maven循环依赖问题 您在构建Maven项目时遇到的错误信息表明存在**模块间循环依赖**。具体错误为: ``` Edge between 'Vertex{label='com.ruoyi:ruoyi-common:3.8.8'}' and 'Vertex{label='com.ruoyi:ruoyi-system:3.8.8'}' introduces a cycle in the graph com.ruoyi:ruoyi-system:3.8.8 --> com.ruoyi:ruoyi-common:3.8.8 --> com.ruoyi:ruoyi-system:3.8.8 ``` #### 问题本质 循环依赖发生在两个模块相互引用时: 1. `ruoyi-system` 模块依赖 `ruoyi-common` 2. `ruoyi-common` 模块又反向依赖 `ruoyi-system` 形成闭环:$$ \text{system} \rightarrow \text{common} \rightarrow \text{system} $$ #### 解决方案(逐步操作) ##### 1. **定位循环依赖点** - 检查 `ruoyi-common/pom.xml` 中是否显式依赖了 `ruoyi-system`: ```xml <!-- 错误示例 --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-system</artifactId> <version>3.8.8</version> </dependency> ``` - 若存在,**直接删除此依赖**(这是最快解决方案) ##### 2. **代码级依赖检查** 如果POM中无显式依赖,检查代码: - 在 `ruoyi-common` 模块中搜索 `ruoyi-system` 中的类引用 - 常见场景: - `ruoyi-common` 导入了 `ruoyi-system` 的Service/Entity类 - `ruoyi-common` 的工具类引用了系统模块的功能 ##### 3. **架构重构(核心方案)** ```mermaid graph LR A[原结构] --> B[ruoyi-system] --> C[ruoyi-common] C --> B %% 循环点 D[新结构] --> E[ruoyi-system] D --> F[ruoyi-common] D --> G[新建 ruoyi-common-core] F --> G %% 基础层 E --> G %% 共同依赖基础层 ``` - **步骤**: 1. 创建新模块 `ruoyi-common-core` 2. 将 `ruoyi-common` 中被 `ruoyi-system` 依赖的**基础代码**移至 `core` 模块 3. 调整依赖关系: - `ruoyi-common` 依赖 `ruoyi-common-core` - `ruoyi-system` 依赖 `ruoyi-common-core` - 确保 `ruoyi-common-core` **不依赖**其他业务模块 ##### 4. **依赖作用域优化** 若循环依赖无法完全消除,使用 `<scope>` 限制: ```xml <!--ruoyi-common 中 --> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-system</artifactId> <version>3.8.8</version> <scope>provided</scope> <!-- 编译时不传递 --> </dependency> ``` #### 验证修复 执行以下命令验证: ```bash mvn clean install -DskipTests ``` 循环依赖错误应消失,构建成功[^1][^2]。 #### 预防措施 1. 遵循 **单向依赖原则**:基础模块(如common)永远不依赖上层模块 2. 定期运行依赖分析: ```bash mvn dependency:tree -Dincludes=com.ruoyi ``` 3. 使用架构守护工具(如ArchUnit)禁止跨模块反向引用 > **关键提示**:在RuoYi等标准框架中,`common` 模块通常是所有模块的基础层,若出现反向依赖,往往是代码结构问题,需优先检查模块职责划分[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值