- Java 21+
- Node.js 18+
- pnpm
# 构建插件
./gradlew build
# 开发前端
cd ui
pnpm install
pnpm dev./gradlew build- 默认值设定由
src/main/resources/extensions/settings.yaml统一定义。 - 配置类、
fallbackConfig()、配置提供方(Supplier)和运行时初始化逻辑中,不要重复写同一份业务默认值。 - 设置缺失、读取失败或对象为空时,可以返回最小可用的兜底配置;但兜底配置不应再维护一份和
settings.yaml并行的业务默认值。 - 如果某个配置项在运行时可能出现非法值,并且会导致初始化失败,或者旧数据、脏数据可能绕过界面约束,应在消费前校验,必要时修正为可用值,而不是在配置类中再补一套默认值。
- processUiResources - 处理UI前端资源,将ui子项目的构建输出复制到console目录
- processShikiResources - 处理Shiki代码高亮的JS资源,仅完整版需要
- processLiteResources - 专门为轻量版处理资源,排除JS相关内容
- jarLite - 构建轻量版,完全排除JS功能和Javet依赖
- jarFullAllPlatforms - 构建包含所有平台支持的完整版
- jarFull{Platform} - 构建特定平台的完整版(如jarFullLinux-x86_64)
- buildAll - 构建所有版本
- buildLite - 仅构建轻量版
- build/jar - 默认构建包含所有平台支持的完整版
构建完成后,可以在 build/libs 目录找到插件 jar 文件。
以下问题属于当前架构和上游依赖形态带来的限制,开发时需要明确认知。
- 相关实现:
src/main/java/top/howiehz/halo/plugin/extra/api/service/interop/web/filter/htmlminify/HtmlMinifyWebFilter.java
- 当前 HTML 页面压缩功能依赖 Halo 的附加 Web 过滤器扩展点(
AdditionalWebFilter)完整读取并重写响应体。 - 这意味着在压缩前必须先聚合完整的 HTML 响应内容,再交给
minify-html处理。 - 因此该功能天然会带来一次额外的内存占用和复制成本,无法像真正的流式转换那样边读边压。
- 这不是当前实现的疏漏,而是 Halo 附加 Web 过滤器扩展点(
AdditionalWebFilter)的接入方式和minify-html接口形态共同决定的限制。
本项目将 JavaScript 工具嵌入到 Java 运行时中,并将其预加载到 Javet V8 运行时中。按照以下步骤添加对新 JS 模块的支持。
- 文件:
src/main/java/top/howiehz/halo/plugin/extra/api/service/interop/runtime/module/JsModule.java - 为模块添加一个枚举常量。UMD 模块
marked的示例:
MARKED("marked", "marked.umd.js", JsModuleType.UMD),- 该枚举将
module.getModuleName()映射到js/<name>。getSourceCode()将加载resources/js/<fileName>。
- 路径:
src/main/resources/js/ - 名称必须与您添加的
fileName匹配。示例:marked.umd.js。 - 该文件可以是真实的库构建文件或精简的 UMD 文件。对于 ESM 或其他模块类型,请相应调整
JsModuleType。
- 文件:
src/main/java/top/howiehz/halo/plugin/extra/api/service/interop/runtime/engine/CustomJavetEngine.java - 引擎当前在
preloadModules()中预加载Shiki - 使用
JsModule.MARKED.getSourceCode()读取资源,使用v8Runtime.getExecutor(code).executeVoid()执行。 - 加载后,验证预期的函数是否暴露在
globalThis这类全局入口上。保持预加载对错误的容忍性,避免引擎创建失败。
- 在
service/interop/runtime/adapters/<module>下创建服务接口(示例service/interop/runtime/adapters/marked/MarkedService.java),定义您需要的操作。 - 使用 Spring
@Service类实现接口,该类使用现有的V8EnginePoolService对运行时执行调用,类似于ShikiHighlightServiceImpl。 - 优先读取
globalThis上的方法(例如parseMarkdown)或globalThis.<lib>.parse。
- 使用
V8EnginePoolService.executeScript或withEngine调用函数并验证结果。 - 在
CustomJavetEngine.preloadModules()中添加快速布尔检查,如typeof parseMarkdown === 'function'并记录结果。
- JsModuleType.UMD:直接执行脚本(UMD 通常会挂到
globalThis上)。 - JsModuleType.ESM:
CustomV8ModuleResolver编译并为 ESM 模块返回 IV8Module。 - JsModuleType.CJS:CommonJS 模块使用模拟的
module.exports对象执行,导出作为模块对象返回。
- 从项目根目录运行:
gradlew.bat clean assemble -x test- 如果编译成功,通过启动主机应用程序或执行新服务的单元测试来在运行时测试功能。
- 保持嵌入的 JS 文件相对较小,以保持 jar 大小可管理。
- 优先选择精简或压缩的 UMD 构建版本进行嵌入。
- 如果库暴露异步接口(Promise),Java 实现应使用 Javet 的 Promise 辅助工具或
V8ValuePromise轮询等待结果。 - 添加单元测试,使用模拟或引擎池来验证解析/高亮行为。