免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Halo 插件 UI 资源目录解析:从 console 到 ui 的偏好策略与静态资源路由设计

Halo 插件 UI 资源目录解析:从 console 到 ui 的偏好策略与静态资源路由设计 Halo 插件 UI 资源目录解析从 console 到 ui 的偏好策略与静态资源路由设计【免费下载链接】haloHalo 是一款强大易用的开源建站工具从个人博客、知识库到企业官网、在线商城Halo 都能助您轻松实现一站式满足您的多样化建站需求。项目地址: https://gitcode.com/GitHub_Trending/ha/halo导读本文围绕 Halo 仓库openspec变更档案中的plugin-ui-resources规格展开讲解 Halo 插件前端资源目录的演进插件打包产物main.js、style.css如何从历史遗留的console目录平稳迁移到跨平台共享的ui目录。读完本文你将理解 Halo 在“静态资源路由、聚合 Bundle 选择、Plugin.status状态链接”三个层面上的目录选择逻辑掌握/plugins/{name}/assets/ui/**与/plugins/{name}/assets/console/**两套 URL 的语义差异以及插件作者如何在不破坏老插件的前提下完成迁移。本文主体以 openspec/changes/archive/2026-05-29-prefer-plugin-ui-resources/specs/plugin-ui-resources/spec.md 为骨架结合同目录下的 design.md、proposal.md 与 tasks.md并下沉到 Halo 源码验证实现细节。背景为什么要有ui与console两个目录历史包袱名字叫console的共享目录Halo 早期的插件前端 BundleJS/CSS统一从resources/console目录发现并加载。这个目录名匹配的是“仅面向 Console 控制台集成”的时代。随着 Halo 支持 UC 个人中心平台Console 与 UC 开始共享同一套插件 UI 资源机制而共享目录却仍沿用console这一有误导性的命名。设计文档 design.md 对此有一段直白的说明插件前端 Bundle 目前是从遗留的console资源目录发现并对外提供服务的该命名与最初的 Console-only 集成相对应但 Halo 现已支持 UC 平台两个平台共享插件 UI 资源——因此运行时需要把ui识别为首选的共享资源目录同时不能破坏仍然打包console/main.js与console/style.css的存量插件。三个受影响的关键面该变更涉及三条既有链路静态资源对外服务/plugins/{name}/assets/console/**路径下的静态文件聚合插件 Bundle从每个已启动插件的main.js与style.css生成聚合产物状态回填Plugin.status.entry与Plugin.status.stylesheet链接在 reconcile协调流程中被填充。Proposalproposal.md中为此定义了新的能力项plugin-ui-resources其含义即“Halo 如何解析、服务、聚合与上报插件 UI 资源目录”。需求规格逐条解读规格文件正文为三个ADDED Requirements新增需求每条均附带 WHEN/THEN 形式的可验证场景。下面按原文完整继承并逐条展开。需求一插件 UI 资源路由Plugin UI asset routesRequirement: Plugin UI asset routesHalo SHALL serve plugin static assets from both the preferred shared UI resource route and the legacy Console resource route. Halo 应当同时通过“首选的共享 UI 资源路由”和“遗留的 Console 资源路由”对外提供插件静态资源。场景WHEN前置条件THEN预期结果Serve assets from ui route插件在ui/main.js提供资源Halo 使其可通过/plugins/{name}/assets/ui/main.js访问Preserve console asset route插件在console/main.js提供资源Halo 继续使其可通过/plugins/{name}/assets/console/main.js访问可见路由是**目录相关directory-specific**的console段 URL 解析console/**ui段 URL 解析ui/**。设计文档明确否决了“把/assets/console/**别名到ui/**”的方案理由是保持旧 URL 稳定让新uiURL 显式可读同时避免“URL 里写着console却静默返回ui资源”这种反直觉行为。需求二插件 Bundle 目录偏好Preferred plugin bundle directoryRequirement: Preferred plugin bundle directoryHalo SHALL select the plugin bundle directory per plugin usinguibeforeconsole. Halo 应当按插件粒度选择 Bundle 目录优先级ui高于console。场景WHEN前置条件THEN预期结果Prefer ui bundle directory插件提供ui/main.js或ui/style.cssHalo 为该插件使用ui目录下的 Bundle 资源不再聚合该插件console目录下的 Bundle 资源Fall back to console bundle directory插件既不提供ui/main.js也不提供ui/style.css若存在则使用console/main.js与console/style.css注意两个关键词按插件per plugin选择目录而非按单个文件选择任一只存在即命中目录“可用”的判据是其中至少存在一个已知 UI Bundle 资源main.js或style.css。设计文档强调这是为了避免混合结果——例如ui/main.js搭配console/style.css会让运行期行为取决于开发者是否碰巧打包了一半的ui资源。目录级判定把行为锁定为确定性的。需求三插件状态中的 Bundle URLPlugin status bundle URLsRequirement: Plugin status bundle URLsHalo SHALL report plugin status entry and stylesheet URLs from the selected plugin bundle directory. Halo 应当依据最终选定的插件 Bundle 目录来上报状态中的入口与样式表 URL。场景WHEN前置条件THEN预期结果Status uses ui links when ui is selected插件提供ui/main.js或ui/style.cssPlugin.status.entry在存在ui/main.js时指向/plugins/{name}/assets/ui/main.jsPlugin.status.stylesheet在存在ui/style.css时指向/plugins/{name}/assets/ui/style.cssStatus falls back to console links插件未提供ui/main.js或ui/style.cssPlugin.status.entry在存在console/main.js时指向/plugins/{name}/assets/console/main.jsPlugin.status.stylesheet在存在console/style.css时指向/plugins/{name}/assets/console/style.css这条需求的意义在于状态输出反映真实的运行期资源来源。设计文档指出这能让管理员与插件工具链清晰地看出当前插件实际使用的是哪种资源布局避免“聚合走ui、状态却报console”之类的口径分裂。设计决策目录级选择、路由隔离、状态一致决策 1目录选择是插件级的不是文件级的选择顺序固定为uiconsole目录被认定为“可用”的条件是其中至少存在一个已知 UI Bundle 资源main.js或style.css。一旦某插件选中uiHalo 就不会再聚合或上报该插件console目录下的 Bundle 文件。这样既规避了部分打包造成的混乱也让插件作者面对的行为完全可预期。决策 2资源路由保持目录专属已有路由/plugins/{name}/assets/console/**继续从console/**解析资源新增路由/plugins/{name}/assets/ui/**从ui/**解析资源二者互不串扰。决策 3状态 URL 来自所选目录Plugin.status.entry与Plugin.status.stylesheet与聚合流程共用同一个“已选目录”。选ui就生成/assets/ui/...否则走遗留/assets/console/...。非目标Non-Goals本次变更有明确的边界未涉及不改变公开插件扩展 API 或Plugin模型 schema不重新生成 OpenAPI 客户端不修改插件构建工具默认行为不做/assets/console/**到ui/**的别名映射。源码级实现剖析规格与设计文档中描述的行为在当前仓库中可一一对应到具体实现。1) 目录常量与选择算法BundleResourceUtils核心工具类位于 application/src/main/java/run/halo/app/plugin/resources/BundleResourceUtils.java其中定义了UI_BUNDLE_LOCATION ui、CONSOLE_BUNDLE_LOCATION console见 #L30-L31JS_BUNDLE main.js、CSS_BUNDLE style.css见 #L32-L33优先级数组BUNDLE_LOCATIONS {UI_BUNDLE_LOCATION, CONSOLE_BUNDLE_LOCATION}见 #L35其顺序即“先ui后console”数组尾部还留有注释TODO(Halo 3): Remove after legacy IIFE UI provider support ends.说明这是服务于遗留 IIFE UI provider 的过渡机制在 Halo 3 中有望移除。目录选择算法实现在selectBundleLocation见 #L116-L126public static Nullable String selectBundleLocation(DefaultResourceLoader resourceLoader) { Assert.notNull(resourceLoader, Resource loader must not be null); for (String location : BUNDLE_LOCATIONS) { var jsBundle getBundleResource(resourceLoader, location, JS_BUNDLE); var cssBundle getBundleResource(resourceLoader, location, CSS_BUNDLE); if (jsBundle ! null || cssBundle ! null) { return location; } } return null; }这段代码正是规格中“目录可用 main.js或style.css任一项存在”的落地按ui→console顺序扫描找到第一个至少含一个 Bundle 文件的目录即返回。配套 API 还包括getSelectedBundleResource(PluginManager, String pluginName, String bundleName)#L42-L56先selectBundleLocation再从已选目录中取指定 Bundle 资源——这是聚合流程的入口getBundleResource(...)#L128-L137将bundleLocation与bundleName组合为类路径并加载加载前通过FileUtils.checkDirectoryTraversal(...)做目录穿越防护#L134buildAssetUrl(...)#L93-L105按/plugins/{pluginName}/assets/{bundleLocation}/{resourceName}拼 URL若有版本号则追加?v查询参数assertSupportedBundleLocation(...)#L139-L146仅接受ui与console两个目录其余直接抛IllegalArgumentException。2) 双路由注册与静态资源响应PluginAutoConfigurationWebFlux 路由注册在 application/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java 的pluginJsBundleRouteBean 中#L65-L78return RouterFunctions.route() .GET( /plugins/{name}/assets/ui/{*resource}, request - getResourceResponse(pluginManager, cacheProperties, UI_BUNDLE_LOCATION, request)) .GET( /plugins/{name}/assets/console/{*resource}, request - getResourceResponse(pluginManager, cacheProperties, CONSOLE_BUNDLE_LOCATION, request)) .build();注意两条.GET分别把常量UI_BUNDLE_LOCATION与CONSOLE_BUNDLE_LOCATION作为“目录基座”传给同一套处理逻辑getResourceResponse#L80-L109实现上从路径中取出{name}插件名与{resource}资源相对路径用BundleResourceUtils.getBundleResource在指定目录下解析资源资源不存在时返回404 Not Found存在时套用 Spring Boot 的静态资源缓存策略Cache-Control、Last-Modified条件请求等。因此/plugins/{name}/assets/ui/main.js与/plugins/{name}/assets/console/main.js共享同一套响应与缓存逻辑仅仅是“目录参数”不同——这与设计文档“路由保持目录专属、逻辑可复用”的意图完全吻合。3) 状态回填PluginReconciler协调器 application/src/main/java/run/halo/app/core/reconciler/PluginReconciler.java 中的resolveStaticResources方法#L524-L588承担Plugin.status的资源类字段回填var resourceLoader BundleResourceUtils.getResourceLoader(pluginManager, pluginName); if (resourceLoader null) { return null; } var bundleLocation BundleResourceUtils.selectBundleLocation(resourceLoader); if (bundleLocation null) { return null; } var entryRes BundleResourceUtils.getBundleResource(resourceLoader, bundleLocation, BundleResourceUtils.JS_BUNDLE); var cssRes BundleResourceUtils.getBundleResource(resourceLoader, bundleLocation, BundleResourceUtils.CSS_BUNDLE);随后#L571-L586用UriComponentsBuilder按plugins/{pluginName}/assets/{bundleLocation}/main.js或style.css拼 URL并附带版本号?version...分别写入status.setEntry(...)与status.setStylesheet(...)若bundleLocation为null即ui/console均无 Bundle则该方法提前返回状态字段保持前面重置的null#L532-L533。值得注意的是版本号处理#L527-L530开发模式下插件版本号未必每次都变化此时会以clock.instant().toEpochMilli()作为版本参数确保前端能拿到更新后的资源而不被缓存卡住。这与需求三“状态 URL 来自被选目录”一一对应entry/stylesheet中出现的路径段正是selectBundleLocation的返回值。4) 聚合入口PluginServiceImpl聚合 Bundle 流程用于把每个已启动插件的main.js/style.css聚合为平台页面统一加载的产物在 application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java 中通过调用BundleResourceUtils.getSelectedBundleResource(...)#L298获取插件资源。由于该方法内部先执行目录选择聚合层无需关心插件到底是ui还是console布局——选择结果天然一致也即设计文档“让 JS、CSS 与状态 URL 的目录选择保持一致”的实现基础。迁移、兼容性与回滚存量插件零改动设计文档的迁移计划指出无需数据迁移。仍把前端资源打包在resources/console下的插件会经由console兜底逻辑继续正常工作。新插件/欲迁移插件怎么做插件作者只需把共享前端资源打包到resources/ui下。一旦ui/main.js或ui/style.css存在Halo 便对该插件使用ui目录随后静态文件通过/plugins/{name}/assets/ui/...暴露聚合产物取自ui/main.js、ui/style.cssPlugin.status.entry、Plugin.status.stylesheet上报/assets/ui/...链接。⚠️ 迁移注意由于是目录级判定部分打包会“屏蔽”同插件遗留的console资源。例如插件只补了ui/main.js却忘了ui/style.css那么控制台样式将无从加载console/style.css不会被回退使用。迁移时应一次补全ui目录下所需的全部 Bundle 文件。动态 chunk 的公共路径设计文档“风险与权衡”明确提示若插件采用动态代码分割dynamic chunks打包产物里的 chunk 公共路径必须与打包目录匹配。本次新增了/assets/ui/**的服务但并不会改写旧的 chunk public path因此使用自有分包路径的插件需自行确认输出 URL 指向实际所在目录。回滚回滚同样直接撤销运行时变更即恢复旧的“仅 console 目录”行为仍以console打包的插件不受任何影响。测试与验证本次能力的落地经过了系统化验证。按 tasks.md 的验收清单资源解析覆盖ui优先级、console兜底、以及目录级“选中ui后跳过console”的 Bundle 资源单测路由覆盖/assets/ui/**的静态资源服务与/assets/console/**的既有服务保留协调器覆盖两种选中目录下entry/stylesheet状态 URL 的生成收尾验证运行./gradlew :application:spotlessApply统一格式执行插件 Bundle 资源、插件资源路由、插件协调相关的聚焦应用测试并通过openspec validate prefer-plugin-ui-resources --strict规格校验。这也为插件作者提供了行为契约层面的确定性无论目录布局如何Halo 对外呈现的 URL 与状态始终来自同一次目录选择的结果。小结plugin-ui-resources变更以最小侵入的方式完成了 Halo 插件前端资源的“目录正名”在三条链路静态路由、Bundle 聚合、状态 URL上统一引入“ui优先、console兜底”的目录级选择策略同时借助BundleResourceUtils的单一选择算法保证口径一致。对插件作者而言只需记住两条规则目录选择是按插件、按目录整体发生的以及ui目录必须完整提供main.js/style.css才会被采用。这套机制让既有console插件平稳运行至今也让新插件能够渐进迁移到跨 Console 与 UC 共享的ui资源布局上。【免费下载链接】haloHalo 是一款强大易用的开源建站工具从个人博客、知识库到企业官网、在线商城Halo 都能助您轻松实现一站式满足您的多样化建站需求。项目地址: https://gitcode.com/GitHub_Trending/ha/halo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表