Skip to content

Latest commit

 

History

History
127 lines (85 loc) · 5.43 KB

File metadata and controls

127 lines (85 loc) · 5.43 KB

贡献指南

开发环境

  • 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 文件。

已知问题

以下问题属于当前架构和上游依赖形态带来的限制,开发时需要明确认知。

HTML 页面压缩的响应体改写不是流式的

  • 相关实现:
    • 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 接口形态共同决定的限制。

如何添加新的嵌入式 JS 模块

本项目将 JavaScript 工具嵌入到 Java 运行时中,并将其预加载到 Javet V8 运行时中。按照以下步骤添加对新 JS 模块的支持。

JsModule 枚举中添加条目

  • 文件: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>

将 JS 文件放在 resources 目录下

  • 路径: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 这类全局入口上。保持预加载对错误的容忍性,避免引擎创建失败。

暴露 Java 服务来调用模块

  • service/interop/runtime/adapters/<module> 下创建服务接口(示例 service/interop/runtime/adapters/marked/MarkedService.java),定义您需要的操作。
  • 使用 Spring @Service 类实现接口,该类使用现有的 V8EnginePoolService 对运行时执行调用,类似于 ShikiHighlightServiceImpl
  • 优先读取 globalThis 上的方法(例如 parseMarkdown)或 globalThis.<lib>.parse

验证

  • 使用 V8EnginePoolService.executeScriptwithEngine 调用函数并验证结果。
  • 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 轮询等待结果。
  • 添加单元测试,使用模拟或引擎池来验证解析/高亮行为。