FIT2CLOUD
API 扩展包 的图标

API 扩展包

4,600
1,578

字数统计、中英混排优化、HTML 压缩、语法高亮……

API 扩展包

GitHub
GitHub all releases
GitHub release (latest by date)
GitHub repo size
Halo Version

简介

一个为 Halo CMS 提供额外 API 的轻量级插件。

快捷跳转:版本说明/全量版使用须知/文档目录

核心理念

CMS 的价值就是在服务端管理和处理内容,为什么要把简单的数据处理推到前端去增加复杂度呢?

让 CMS 回归"内容即数据"的本质,减少不必要的前端异步请求和动态渲染。

这个插件正是基于这个理念:

  • 让复杂的逻辑在后端处理
  • 前端模板只负责展示
  • 为主题提供简洁的 Finder API
  • 减少不必要的 JavaScript 依赖
前端动态加载方式 vs 后端服务端渲染方式
对比维度 前端动态加载方式 后端服务端渲染方式
全页无刷兼容性(Pjax/Swup) ❌ 需要额外处理动态内容加载 ✅ 模板渲染,天然支持无刷
性能表现 ❌ 需要额外 HTTP 请求,增加延迟 ✅ 服务端渲染,一次性输出
用户体验 ❌ 页面闪烁,先显示占位符后填充数据 ✅ 内容立即可见,无加载状态
SEO 友好 ❌ 搜索引擎难以抓取动态内容 ✅ 服务端渲染,完全 SEO 友好
错误处理 ❌ 需要处理网络失败、超时等异常 ✅ 服务端统一异常处理,减轻主题作者心智负担
开发复杂度 ❌ 需要编写 JS 代码、状态管理、DOM 操作 ✅ 模板中直接调用,代码简洁
缓存策略 ❌ 需要前端缓存逻辑或重复请求 ✅ 可利用模板缓存和服务端缓存
首屏渲染 (FCP) ❌ 需要等待 JS 执行和 API 响应 ✅ HTML 直接包含内容,渲染更快
最大内容绘制 (LCP) ❌ 动态内容加载延迟主要内容显示 ✅ 关键内容随页面一起渲染
累积布局偏移 (CLS) ❌ 内容异步加载可能导致页面跳动 ✅ 静态布局,无意外的布局变化
交互响应 (INP) ❌ JS 执行和 DOM 操作影响交互性能 ✅ 减少 JS 负担,交互更流畅

功能介绍

本插件现版本已提供以下功能:

欢迎为此插件提 Issue,任何你需要的功能都可以在此处提出,我将在能力范围内尽力实现。

💖 支持本项目: 如果你觉得这个插件有用,点个 Star 就是对我最大的鼓励!

感谢所有支持本项目的用户和开发者,也特别感谢 Halo CMS 团队为插件生态提供的优秀平台。

Finder API 文档

文档类型定义

  • string: 字符串类型
  • int:整数类型(实现为 BigInteger 即无限精度整数)
  • boolean:布尔类型(true/false)
  • map:映射类型(键值对)

插件本体信息相关 API

检测本插件是否启用

描述

检测 ExtraAPI 插件是否已安装并启用。建议在主题中使用本插件 API 前先进行检测,以确保插件可用性。
官方文档:插件 Finder API

参数

  • extra-api
  • 解释:本插件的标识符(metadata.name

返回值

  • 类型:boolean
  • 解释:插件可用时返回 true,否则返回 false

说明

使用 pluginFinder.available('extra-api')
可以优雅地处理插件依赖,避免在插件未安装时出现模板错误,提升主题的兼容性和用户体验。
注:在此基础上可以使用 pluginFinder.available('extra-api', '3.*') 锁定大版本号,避免 API 破坏性更新时导致主题渲染报错。

示例

<!--/* 先检测插件可用性,再使用 API */-->
<th:block th:if="${pluginFinder.available('extra-api')}">
    <span
            th:text="|总字数:${extraApiStatsFinder.getPostWordCount()}|"
    ></span>
</th:block>

<!--/* 写在一个标签内也可以,th:if 的优先级比 th:text 高 */-->
<span
        th:if="${pluginFinder.available('extra-api')}"
        th:text="|总字数:${extraApiStatsFinder.getPostWordCount()}|"
></span>

<!--/* 自然模板写法 */-->
<span th:if="${pluginFinder.available('extra-api')}">总字数:[[${extraApiStatsFinder.getPostWordCount()}]]</span>

插件版本检测 API

Finder 名称: extraApiPluginInfoFinder

描述

提供插件版本类型检测功能,让主题或其他代码能够检测当前运行的是轻量版还是全量版插件,以便有条件地使用高级功能。
(注:以下四个 API 本质上是同一个 API,您可以选择使用其中任何一个进行主题编写。)

API 方法

// 检查是否为全量版
extraApiPluginInfoFinder.isFullVersion()

// 检查是否为轻量版  
extraApiPluginInfoFinder.isLiteVersion()

// 获取版本类型字符串
extraApiPluginInfoFinder.getVersionType()

// 检查 JavaScript 功能是否可用
extraApiPluginInfoFinder.isJavaScriptAvailable()

参数

返回值

  • isFullVersion()
    • 类型:boolean
    • 解释:全量版时返回 true,轻量版时返回 false
  • isLiteVersion()
    • 类型:boolean
    • 解释:轻量版时返回 true,全量版时返回 false
  • getVersionType()
    • 类型:string
    • 解释:返回 "full"(全量版)或 "lite"(轻量版)
  • isJavaScriptAvailable()
    • 类型:boolean
    • 解释:JavaScript 功能可用时返回 true

补充说明

  • 检测原理
    • 通过检查 V8EnginePoolService 类是否存在来判断版本类型:
      • 全量版:包含 JavaScript 运行时,V8EnginePoolService 类存在
      • 轻量版:构建时排除原生运行时相关包(interop 包)下所有类,V8EnginePoolService 类不存在
  • 应用场景
    • 主题兼容性:主题可以根据插件版本提供不同的功能体验
    • 用户提示:向用户说明当前版本的功能限制
    • 条件渲染:仅在支持的版本中启用特定功能(主题请求不存在的 Finder API 会报错并阻止页面渲染)
  • 性能说明
    • 这些方法单次调用开销极小,适合在模板中频繁使用
    • 首次调用性能开销比后续调用稍高,但仍在毫秒级别(首次通过反射检查,后续直接读取缓存)

使用示例

<!--/* 根据插件版本条件性显示 */-->
<th:block th:unless="${extraApiPluginInfoFinder.isFullVersion()}">
    <p>当前使用轻量版插件,代码高亮功能不可用</p>
</th:block>

<!--/* 显示当前版本类型 */-->
<span>插件版本:[[${extraApiPluginInfoFinder.getVersionType()}]]</span>

<!--/* 检查 JavaScript 功能可用性 */-->
<div th:if="${extraApiPluginInfoFinder.isJavaScriptAvailable()}">
    <p>JavaScript 功能可用,支持高级渲染功能</p>
</div>

<!--/* 结合其他条件使用,下面这段代码可直接用于 /templates/post.html */-->
<th:block th:if="${pluginFinder.available('extra-api') and extraApiPluginInfoFinder.isFullVersion()}">
    <!-- 只有在插件可用且为全量版时才显示 -->
    <div th:utext="${extraApiJsRenderFinder.highlightCodeInHtml(post.content?.content)}"></div>
</th:block>

统计信息 API

Finder 名称: extraApiStatsFinder

文章字数统计 API

描述

提供文章字数统计功能,支持统计单篇文章或全站文章的字数总和。可选择统计已发布版本或最新版本(含草稿)。适用于显示文章字数、阅读时间估算等场景。

API 方法(无参数)

// 统计全部文章已发布版本的总字数
extraApiStatsFinder.getPostWordCount()

参数

返回值

  • 类型:int
  • 解释:字数统计结果(非负),不存在或参数缺失时返回 0

API 方法(传入映射形式参数)

// 传入映射形式参数
extraApiStatsFinder.getPostWordCount({
  name: 'post-metadata-name',  // 可选,未传入则统计全部文章字数总和
  version: 'release' | 'draft'  // 可选,默认 'release'
})

参数

  • 映射形式参数:
    • name
      • 类型:string
      • 解释:文章的 metadata.name,可选参数。未传入时统计全部文章字数总和。
    • version
      • 类型:string
      • 解释:统计版本,可选参数。release(未传入时默认值)或 draft

返回值

  • 类型:int
  • 解释:字数统计结果(非负),不存在或参数缺失时返回 0

补充说明

  • 计数规则:
    • 中文、日文、韩文等 CJK 字符按每个字符计 1。
    • ASCII 连续字母/数字按 1 个单词计数。
    • 标点符号和空格不计入统计。
  • 错误处理:
    • 输入为空或文章不存在时返回 0,不会抛出异常。
  • 性能说明:
    • 单次调用开销较小,适合在模板中直接使用。
    • 启动时自动计算并缓存,仅在文章内容更新时重新计算。

使用示例

<!--/* 统计文章已发布版本的字,下面这段代码可直接用于 /templates/post.html */-->
<span th:text="${extraApiStatsFinder.getPostWordCount({name: post.metadata.name})}"></span>

<!--/* 统计文章最新版本的字数(含草稿),下面这段代码可直接用于 /templates/post.html */-->
<span th:text="${extraApiStatsFinder.getPostWordCount({name: post.metadata.name, version: 'draft'})}"></span>

<!--/* 统计全站已发布文章的总字数,下面这段代码可直接用于全部模板 */-->
<span th:text="${extraApiStatsFinder.getPostWordCount()}"></span>
<!--/* 与下方写法等价 */-->
<span th:text="${extraApiStatsFinder.getPostWordCount({})}"></span>

<!--/* 统计全站所有文章最新版本的总字数(含草稿),下面这段代码可直接用于全部模板 */-->
<span th:text="${extraApiStatsFinder.getPostWordCount({version: 'draft'})}"></span>

HTML 内容字数统计 API

描述

提供 HTML 内容字数统计功能,支持统计任意 HTML 字符串的字数。适用于统计文章内容、瞬间内容或自定义 HTML 片段的字数。

API 方法(传入字符串形式参数)

// 直接传入 HTML 内容字符串进行统计
extraApiStatsFinder.getContentWordCount(htmlContent)

参数

  • htmlContent
    • 类型:string
    • 解释:HTML 内容字符串(必需)。即要统计字数的 HTML 内容,支持标准 HTML 格式。

返回值

  • 类型:int
  • 解释:字数统计结果(非负),输入为空时返回 0

API 方法(传入映射形式参数)

// 传入映射形式参数
extraApiStatsFinder.getContentWordCount({
  htmlContent: '<p>HTML 内容</p>'  // 必需,要统计字数的 HTML 内容
})

参数

  • 映射形式参数:
    • htmlContent
      • 类型:string
      • 解释:HTML 内容字符串(必需)

返回值

  • 类型:int
  • 解释:字数统计结果(非负),输入为空时返回 0

描述

  • 计数规则:
    • 自动移除 HTML 标签(包括 <script><style> 标签)
    • 中文、日文、韩文等 CJK 字符按每个字符计 1
    • ASCII 连续字母/数字按 1 个单词计数
    • 标点符号和空格不计入统计
  • 错误处理:
    • 输入为空或 null 时返回 0,不会抛出异常
  • 性能说明:
    • 单次调用开销较小,适合在模板中直接使用
    • 纯计算操作,不涉及缓存和数据库查询

使用示例

<!--/* 统计任意 HTML 内容的字数 */-->
<!--/* 传入映射形式参数的写法 */-->
<span th:text="${extraApiStatsFinder.getContentWordCount({htmlContent: '<p>这是一段测试文本</p>'})}"></span>
<!--/* 传入 HTML 内容字符串的写法 */-->
<span th:text="${extraApiStatsFinder.getContentWordCount('<p>这是一段测试文本</p>')}"></span>

<!--/* 统计文章内容的字数,下面这段代码可直接用于 /templates/post.html */-->
<span th:text="${extraApiStatsFinder.getContentWordCount(post.content?.content)}"></span>

<!--/* 统计瞬间内容的字数,下面这段代码可直接用于 /templates/moment.html */-->
<span th:text="${extraApiStatsFinder.getContentWordCount(moment.spec.content?.html)}"></span>

<!--/* 在变量中使用 */-->
<div th:with="wordCount=${extraApiStatsFinder.getContentWordCount(customContent)}">
    <span>字数:[[${wordCount}]]</span>
</div>

渲染 API

Finder 名称: extraApiRenderFinder

中英文混排格式化 API

描述

提供中英文混排自动空格功能,自动在中日韩(CJK)字符与英文字母、数字、符号之间插入空格,提升内容可读性。

处理纯文本内容的 API 方法

// 对纯文本应用 Pangu 空格处理
extraApiRenderFinder.applySpacingInText(text)

参数

  • text
    • 类型:string
    • 解释:要处理的纯文本内容

返回值

  • 类型:string
  • 解释:处理后的内容,失败时返回原始内容

描述

  • 处理规则
    • 在中日韩字符和英文字母之间添加空格
    • 在中日韩字符和数字之间添加空格
    • 在中日韩字符和常见符号之间添加空格
  • 性能说明
    • 纯 Java 实现,处理速度快,适合在模板中直接使用
    • 不涉及缓存,每次调用都会重新处理
  • 错误处理
    • 输入为空或 null 时返回空字符串
    • HTML 解析失败时返回原始内容
    • 不会抛出异常,保证页面渲染稳定性

使用示例

<!--/* 对纯文本应用 Pangu 处理 */-->
<span th:text="${extraApiRenderFinder.applySpacingInText('请问Jackie的鼻子有几个?123个!')}"></span>
<!--/* 输出:请问 Jackie 的鼻子有几个?123 个! */-->

处理 HTML 内容的 API 方法(传入字符串形式参数)

// 对整个 HTML 内容应用 Pangu 空格处理
extraApiRenderFinder.applySpacingInHtml(htmlContent)

参数

  • htmlContent
    • 类型:string
    • 解释:包含 HTML 标签的内容

返回值

  • 类型:string
  • 解释:处理后的内容,失败时返回原始内容

描述

  • 处理规则
    • 在中日韩字符和英文字母之间添加空格
    • 在中日韩字符和数字之间添加空格
    • 在中日韩字符和常见符号之间添加空格
    • 跳过规则:自动跳过 <code><pre><script><style><textarea> 等标签,保留其原始格式
  • 性能说明
    • 纯 Java 实现,处理速度快,适合在模板中直接使用
    • 不涉及缓存,每次调用都会重新处理
  • 错误处理
    • 输入为空或 null 时返回空字符串
    • HTML 解析失败时返回原始内容
    • 不会抛出异常,保证页面渲染稳定性

使用示例

<!--/* 处理整个 HTML 内容(自动跳过 code、pre 等标签) */-->
<div th:utext="${extraApiRenderFinder.applySpacingInHtml(post.content?.content)}"></div>

<!--/* 在变量中使用 */-->
<div th:with="processedContent=${extraApiRenderFinder.applySpacingInHtml(moment.spec.content?.html)}">
    <div th:utext="${processedContent}"></div>
</div>

处理 HTML 内容的 API 方法(传入映射形式参数)

// 使用灵活参数对 HTML 内容应用 Pangu 空格处理
extraApiRenderFinder.applySpacingInHtml({
  htmlContent: '<p>HTML 内容</p>',  // 必需
  selector: 'p'  // 可选,CSS 选择器
})

参数

  • htmlContent
    • 类型:string
    • 解释:包含 HTML 标签的内容(必需)
  • selector
    • 类型:string
    • 解释:CSS 选择器,用于定位特定元素(可选)
    • 支持所有 JSoup CSS 选择器,包括:
      • 标签选择器:p, div, span
      • ID 选择器:#main, #content
      • Class 选择器:.article, .comment
      • 属性选择器:[attr], [attr=value]
      • 后代选择器:div p, .article .content
      • 子选择器:div > p
      • 伪类选择器::first-child, :nth-child(n)

返回值

  • 类型:string
  • 解释:处理后的内容,失败时返回原始内容

描述

描述

  • 处理规则
    • 在中日韩字符和英文字母之间添加空格
    • 在中日韩字符和数字之间添加空格
    • 在中日韩字符和常见符号之间添加空格
    • 省略 selector 时应用跳过规则:自动跳过 <code><pre><script><style><textarea> 等标签,保留其原始格式
  • 性能说明
    • 纯 Java 实现,处理速度快,适合在模板中直接使用
    • 不涉及缓存,每次调用都会重新处理
  • 错误处理
    • 输入为空或 null 时返回空字符串
    • HTML 解析失败时返回原始内容
    • 不会抛出异常,保证页面渲染稳定性

使用示例

<!--/* 使用 CSS 选择器仅处理段落标签 */-->
<div th:utext="${extraApiRenderFinder.applySpacingInHtml({htmlContent: post.content?.content, selector: 'p'})}"></div>

<!--/* 使用 class 选择器处理指定 class 的元素 */-->
<div th:utext="${extraApiRenderFinder.applySpacingInHtml({htmlContent: content, selector: '.article-content'})}"></div>

<!--/* 使用 ID 选择器处理指定 ID 的元素 */-->
<div th:utext="${extraApiRenderFinder.applySpacingInHtml({htmlContent: content, selector: '#main'})}"></div>

<!--/* 使用复合选择器处理 */-->
<div th:utext="${extraApiRenderFinder.applySpacingInHtml({htmlContent: content, selector: 'div.article > p'})}"></div>

<!--/* 处理瞬间内容中的特定元素 */-->
<div th:with="processedContent=${extraApiRenderFinder.applySpacingInHtml({htmlContent: moment.spec.content?.html, selector: '.moment-text'})}">
    <div th:utext="${processedContent}"></div>
</div>

渲染 API(需要 JS 运行时)

此功能仅在全量版中可用。

Finder 名称: extraApiJsRenderFinder

代码高亮 API

extraApiJsRenderFinder.highlightCodeInHtml(htmlContent)

参数

  • htmlContent
    • 类型:string
    • 解释:包含代码块的 HTML 内容,通常是文章或页面的内容字段。

返回值

  • 类型:string
  • 解释:渲染后的 HTML 内容,渲染样式已内联,渲染失败时返回原始内容

描述

使用示例

<!--/* 渲染文章内容中的代码块,下面这段代码可直接用于 /templates/post.html */-->
<div th:utext="${extraApiJsRenderFinder.highlightCodeInHtml(post.content?.content)}"></div>

<!--/* 在模板中使用,下面这段代码可直接用于 /templates/moment.html */-->
<div th:with="renderedContent=${extraApiJsRenderFinder.highlightCodeInHtml(moment.spec.content?.html)}">
    <div th:utext="${renderedContent}"></div>
</div>

处理器文档

中英文混排格式化处理器

插件提供了自动化的中英文混排格式化处理器,无需在模板中手动调用,即可对文章和页面内容自动应用 Pangu 空格处理。

功能说明

  • 自动处理范围:
    • 处理器会自动处理文章(post)和页面(page)内容中的段落标签(<p>
    • 在中日韩字符与英文、数字、符号之间自动插入空格
  • 处理规则:
    • 自动在 CJK 字符和英文字母之间添加空格
    • 自动在 CJK 字符和数字之间添加空格
    • 自动在 CJK 字符和常见符号之间添加空格
    • 智能跳过 <code><pre><script><style><textarea> 等标签
  • 性能特点:
    • 纯 Java 实现,无需 JavaScript 运行时
    • 处理速度快,对页面加载影响极小
    • 不涉及缓存,每次渲染时实时处理
  • 错误处理:
    • 处理失败时保持原始内容不变
    • 不会因格式化问题影响页面正常渲染

配置选项

此处理器默认启用。开箱即用,无需额外配置。

在“插件设置 - 中英文混排格式化”提供以下配置项:

  • 自动渲染:启用后会自动处理文章和单页中段落标签的中英文混排格式,在中日韩字符与英文、数字、符号之间自动插入空格。通过 Finder 接口调用时不受此配置项影响。

补充说明

  • 此功能使用 Pangu.java 库实现
  • 仅处理可见文本内容,不影响 HTML 结构
  • 递归处理嵌套元素,确保完整覆盖
  • 与本插件提供的其他处理器兼容,互不影响

代码高亮处理器

插件提供了自动化的代码高亮处理器,无需在模板中手动调用,即可对文章和页面内容中的代码块进行语法高亮渲染。

此功能通过 Shiki.js 渲染,仅在全量版中可用。

特点

  • 双主题支持:
    • 可同时渲染浅色和深色主题,便于主题切换
  • 语言自动检测:
    • class 属性中提取语言标识(如 language-javalang-python
  • 容错处理:
    • 渲染失败时保持原始代码块不变
  • 默认自动渲染范围:
    • 处理器会自动处理以下页面内容并在页面 head 注入自定义 CSS 样式:
      • 文章内容 (post)
      • 页面内容 (page)
  • 性能说明:
    • 命中缓存时响应速度极快(微秒级),首次实际调用 JS 渲染时需要 1-3 秒初始化 JS 环境
    • 批量处理策略:
      • 智能分组:根据 V8 引擎池大小动态分配任务(例如 14 个任务 + 5 个引擎 → 5 组,每组 2-3 个任务)
      • 并行执行:多个任务组并行处理,充分利用多核性能
      • 批量渲染:同一组内的任务在单个引擎中通过一次 JS 通信批量处理,减少引擎切换开销
      • 优先缓存:先检查缓存,只对未命中的请求进行实际渲染
    • 缓存策略:
      • LRU + TTL 双重策略:最多缓存 10,000 个代码块,每个条目 24 小时自动过期
      • 智能去重:相同代码内容+语言+主题的重复渲染会自动去重,避免重复计算
      • 缓存键:基于代码内容的 SHA-256 哈希值,避免长代码占用过多内存

配置选项

在“插件设置 - 代码高亮(仅全量版可用)”提供以下配置项:

  • 自动渲染:启用后会自动渲染文章和单页中的代码块。通过 Finder 接口调用时不受此配置项影响。
  • 自定义注入 CSS 样式:启用自动渲染时,会在页面 head 标签中注入样式,以优化 Shiki 渲染效果。默认值提供了边距调整、行号显示,以及基于媒体查询的明暗切换功能。
    • 额外注入规则:指定额外的页面路径规则,支持通配符。
      • 默认包含:/moments/**, /docs/**
      • 支持自定义路径,如 /custom/**
  • 明暗双倍渲染模式切换:
    • 单主题模式:
      • 主题:选择单个代码高亮主题
    • 双主题模式:
      • 亮色主题:浅色模式使用的主题
      • 暗色主题:深色模式使用的主题
      • 亮色主题代码块类名:浅色代码块的 CSS 类名
      • 暗色主题代码块类名:深色代码块的 CSS 类名

支持的主题

插件内置了 118 个 Shiki 主题,包括:

  • GitHub Light/Dark 系列
  • One Light/Dark Pro
  • Nord, Dracula, Monokai
  • Material Theme 系列
  • Catppuccin 系列

完整主题列表: 请在 Halo 管理后台的插件设置页面查看所有可用主题。或前往官方文档在线预览。

补充说明

  • 工作原理:
    1. 内容处理阶段:在内容渲染时,处理器会扫描 HTML 中的 <pre><code> 结构
    2. 语言检测:从 class 属性中提取语言标识(如 language-javalang-python
    <!-- 标准 Markdown 格式 -->
    <pre><code class="language-java">public class Hello { }</code></pre>
    
    <!-- 简写格式 -->
    <pre><code class="lang-python">print("Hello World")</code></pre>
    
    <!-- 直接在 pre 标签上指定 -->
    <pre class="language-javascript"><code>console.log("Hello");</code></pre>
    
    1. 主题应用:根据配置应用相应的 Shiki 主题进行高亮
    2. 样式注入:在页面头部注入配置的 CSS
  • 错误处理:
    • 不支持的语言/主题会被跳过渲染
    • 渲染失败时保留原始格式
  • 性能说明:
    • 使用 V8 引擎池和异步处理,提升渲染效率
  • 补充说明:
    • 双主题模式会生成两个并列的 div 元素

HTML 页面压缩处理器

插件提供了自动化的 HTML 页面压缩处理器,无需在模板中手动调用,即可在服务端对前台 HTML 响应进行整体压缩。

此功能通过 minify-html 的 Java JNI 绑定实现,仅在全量版中可用。

特点

  • 压缩范围:
    • 仅处理 GET 请求返回的 HTML 页面响应
    • 仅处理 text/html 且未经过 gzip/br 等编码的响应
    • 默认跳过后台、API、静态资源等路径,避免误处理非页面响应
  • 安全边界:
    • 非 UTF-8 响应会自动跳过,避免字符集被破坏
    • 已编码响应会自动跳过,避免破坏上游压缩结果
    • 压缩失败时自动回退原始 HTML,不影响页面正常返回
  • 性能说明:
    • 处理器会完整读取并重写 HTML 响应体,因此会带来一定 CPU 与内存开销
    • 压缩工作会切换到 Reactor 的弹性线程池(boundedElastic),避免阻塞处理请求的线程
    • 更适合体积较大、访问量稳定、希望进一步压缩 HTML 传输体积的站点

配置选项

在“插件设置 - HTML 页面压缩(仅全量版可用)”提供以下配置项:

  • 自动压缩:启用之后会在服务端对前台 HTML 页面响应做整体压缩。
  • 排除路径规则:
    • 支持 Ant 风格路径匹配,支持 ***
    • 默认包含:/console/**/uc/**/login/**/signup/**/logout/**/themes/**/plugins/**/actuator/**/api/**/apis/**/upload/**
  • 常规安全压缩选项:
    • 压缩内联 CSS
    • 压缩内联 JavaScript
    • 保留花括号模板语法
    • 保留 <% %> 模板语法
    • 保留可省略闭合标签
    • 保留 HTML 注释
    • 保留 html/head 起始标签
    • 保留 input[type=text] 属性
    • 保留 SSI 注释
    • 移除 Bang 声明
    • 移除处理指令
  • 更激进的压缩选项(默认关闭),这些选项可能生成无法通过严格 HTML 校验、但仍可被主流浏览器按 WHATWG 解析规范正确渲染的输出:
    • 压缩 DOCTYPE
    • 允许非规范无引号属性值
    • 允许更激进的实体优化
    • 允许移除属性间空格

关于配置项的原始文档可参考 minify_html

补充说明

  • 此功能使用 minify-html 库实现
  • 处理器只影响页面响应输出,不会修改数据库中的原始内容
  • 如果站点已由 CDN、反向代理或上游网关统一处理 HTML 压缩,请避免重复启用

版本说明

插件提供两个版本:

  • 全量版:包含所有功能,包括代码高亮、HTML 页面压缩等依赖原生运行时的功能。
  • 轻量版:轻量级版本,不包含 JS 相关功能和相关依赖。

轻量版的优势

  • 更小的插件体积
  • 更快的启动速度
  • 更低的内存占用
  • 更低的系统性能要求
  • 支持全平台(全量版仅支持以下平台:Linux ARM64、Linux x86_64、macOS ARM64、macOS x86_64、Windows x86_64)

轻量版本缺少的功能

  • 代码高亮(Shiki.js 渲染)
  • HTML 页面压缩(minify-html JNI)
  • 其他 JS 运行时相关功能

如果您需要上述功能,请使用全量版。

全量版使用须知

⏱️ 首次使用提示: 全量版的 JavaScript 功能(如 Shiki 代码高亮)在首次实际调用时需要 1~3 秒进行环境初始化。这是正常现象,之后的调用速度会非常快。

补充说明:

  • 全量版启动时会初始化 JS 引擎池服务对象。
  • 但不会在启动时立即创建 V8/Node 引擎实例。
  • 真正的引擎实例会在首次实际调用 JS 功能时按需创建;如果未调用相关功能,则不会创建这些引擎实例。

⚠️ 重要: 全量版依赖 Javet 加载 Node.js 原生库(基于 JNI),受 Halo 插件架构限制,存在已知问题

基本使用要求

  1. 请勿热重载更新: 请勿直接覆盖更新(如使用应用商店/插件列表的快捷更新操作)
  2. 正确的更新流程:
    • 方法一:停止插件 → 卸载插件 → 重启 Halo → 安装新版本 → 启动插件
    • 方法二:停止插件 → 卸载插件 → 安装新版本 → 重启 Halo → 启动插件
  3. 卸载推荐做法: 在卸载全量版后重启 Halo 确保原生库资源完全释放。

全量版已知问题

  • 问题一:卸载后重新安装全量版插件,不重启就启用。调用 JS 相关 API 时会出现错误:
    • 首次安装并启动: 正常工作
    • 禁用后重新启用: 正常工作
    • 卸载后重新安装: ❌ 会出现 JavetException: Javet library is not loaded 错误
    • 重启 Halo 后: 恢复正常工作

问题一解决方案

安装好新版本插件后先别启用
在启用新版本插件之前,请先重启 Halo CMS

未来版本会将引擎作为前置依赖插件,更新本插件不再需要重启。

📖 技术原因分析(展开查看详细说明)

根据错误日志和 Halo 架构分析:

问题根源:

  1. Halo 插件架构: 根据 Halo 开发文档 - 插件架构, Halo 使用
    Spring Plugin Framework 实现插件隔离

    • 每个插件拥有独立的 Spring ApplicationContext
    • 每个插件使用独立的 classloader 加载资源
    • 插件间通过 ExtensionPoint 机制通信
    • classloader 完全隔离,无法跨插件共享类或资源
  2. Javet 原生库加载机制:

    • Javet 需要从 JAR 中提取原生库文件(.dll/.so/.dylib)到临时目录
    • JVM 通过 JNI 加载这些原生库文件
    • 原生库文件一旦加载,会被 JVM 锁定
  3. 冲突发生过程:

    • 安装插件时,Javet 提取原生库文件到 C:\Users\用户名\AppData\Local\Temp\javet\进程ID\(以 Windows 举例)
    • JVM 加载这些文件并锁定
    • 卸载插件时,虽然 classloader 被销毁,但原生库文件仍被 JVM 锁定,无法删除
    • 重新安装时,Javet 尝试提取新文件到同一位置
    • 因为文件被锁定,提取失败
    • Javet 初始化失败,报错 Javet library is not loaded because <null>

典型错误日志解读:

WARN - Failed to write to ...libjavet-node-windows-x86_64.v.4.1.7.dll because it is locked
ERROR - Native Library already loaded in another classloader
ERROR - JavetException: Javet library is not loaded because <null>

这三条日志清晰地展示了整个失败过程:

  1. 尝试写入文件失败(文件被锁定)
  2. 检测到库已在其他 classloader 中加载
  3. 初始化失败,因为无法提取必要的库文件

为什么重启有效:

  • 重启 Halo 会终止 JVM 进程
  • JVM 终止时会释放所有文件锁
  • 临时目录中的原生库文件会被清理
  • 新的 Halo 进程启动后,Javet 可以正常提取和加载原生库

为什么 Javet 文档中提到的 JVM 参数无效:

-Djavet.lib.loading.suppress.error=true 这个参数的作用是:

  • 抑制 Javet 在检测到"already loaded in another classloader"时的错误日志
  • 但无法解决文件锁定问题
  • 当库文件无法提取时,Javet 根本无法完成初始化
  • 因此该参数在这个场景下无效

架构层面的限制:

这个问题是 JVM/JNI + 插件 classloader 隔离的固有矛盾:

  • Halo 的插件隔离设计保证了安全性和稳定性
  • 但也导致原生库这类 JVM 级别资源难以管理
  • 类似问题在所有使用 classloader 隔离的插件系统中都存在
  • 这不是 Javet 或本插件的 bug,而是架构层面的限制

相关技术文档:

下载和安装

全量版和轻量版的区别请看:版本说明
使用全量版前请看:全量版使用须知

稳定版

稳定版通过 GitHub Releases 发布,建议生产环境使用。

  1. 访问 Releases 页面
  2. 下载最新版本的 JAR 文件:
    • extra-api-lite-版本号.jar:轻量版(适用于所有平台)
    • extra-api-full-all-platforms-版本号.jar:全量版(包含所有平台依赖,体积较大,不推荐下载)
    • 如需使用全量版,推荐下载平台特定版本:
      • extra-api-full-linux-arm64-版本号.jar:适用于 Linux ARM64 平台的版本
      • extra-api-full-linux-x86_64-版本号.jar:适用于 Linux x86_64 平台的版本
      • extra-api-full-macos-arm64-版本号.jar:适用于 macOS ARM64 平台的版本
      • extra-api-full-macos-x86_64-版本号.jar:适用于 macOS x86_64 平台的版本
      • extra-api-full-windows-x86_64-版本号.jar:适用于 Windows x86_64 平台的版本
  3. 将下载的 JAR 文件上传到 Halo 的插件管理页面安装

开发版

插件的开发版 JAR 文件通过 GitHub Actions 自动构建,仅用于测试和开发。

下载步骤

  1. 访问上述链接,选择最新的成功运行的 workflow。
  2. 在 “Artifacts” 部分,下载 extra-api 压缩包。
  3. 解压后,您将找到以下 JAR 文件:
    • extra-api-lite-版本号-SNAPSHOT.jar:轻量版(适用于所有平台)
    • extra-api-full-all-platforms-版本号-SNAPSHOT.jar:全量版(包含所有平台依赖)
    • extra-api-full-linux-arm64-版本号-SNAPSHOT.jar:全量版(适用于 Linux ARM64 平台)
    • extra-api-full-linux-x86_64-版本号-SNAPSHOT.jar:全量版(适用于 Linux x86_64 平台)
    • extra-api-full-macos-arm64-版本号-SNAPSHOT.jar:全量版(适用于 macOS ARM64 平台)
    • extra-api-full-macos-x86_64-版本号-SNAPSHOT.jar:全量版(适用于 macOS x86_64 平台)
    • extra-api-full-windows-x86_64-版本号-SNAPSHOT.jar:全量版(适用于 Windows x86_64 平台)

选择适合您系统的 JAR 文件安装到 Halo。

开发指南/贡献指南

参见 CONTRIBUTING.md

许可证

AGPL-3.0 © HowieHz


讨论(0)

这是一个开源应用,我们推荐优先在开源仓库中提交 issue,以便开发者更好地接收和跟进问题。

去提交 issue