Halo ESM UI 插件构建规范:ui-plugin-bundler-provider 的格式选择、共享依赖校验与 Vite/Rsbuild 输出契约
Halo 从 Halo 2.26.0 起为插件与主题提供的 Console / User Center 界面模块引入了原生 ESM 加载能力,这一能力由 @halo-dev/ui-plugin-bundler-kit 的现代 Vite/Rsbuild 配置助手承载,其行为契约被记录在 ui-plugin-bundler-provider 规范中。本文以该规范为骨架,结合仓库中 ui-plugin-bundler-kit 源码、设计方案 与 实现任务清单,系统讲解插件/主题 provider 的清单默认读取规则、auto/iife/esm 三种格式选择语义、基于 Halo 版本阈值的快照(snapshot)匹配与共享依赖校验、最小化 provider 清单(ui-plugin.json)以及 Vite 与 Rsbuild 等价输出契约。读完你将能够理解并配置出既支持旧版 IIFE 又能输出可重定位 ESM 产物的 Halo UI 插件与主题构建。
一、规范定位:从 IIFE 拼接到 ESM 双轨的构建侧契约
在 ESM UI 插件能力落地之前,Halo 的插件与主题前端产物是"拼接式"的:所有已启用插件的 main.js 与激活主题的 main.js 被串联执行,PluginModule 通过 window 全局发现,UI 依赖被外置为经典全局脚本,由 Vite 输出 IIFE、Rsbuild 输出 window library。这种模型保证了"单一 Vue 系运行时",但也把 provider 与全局加载顺序耦合在一起,无法使用标准 ESM 分块图,也让 provider 无法可靠核对其构建期依赖是否匹配目标 Halo 版本(详见 design.md 的 Context)。
本仓库关联规范(即 ui-plugin-bundler-provider capability)正是这一改造的构建工具链侧契约。它不重新引入第二个面向用户的兼容版本号,而是要求:provider 依旧只在清单里声明 spec.requires;构建器负责把 spec.requires 翻译成"输出 ESM 还是 IIFE""选中哪份宿主运行时快照""哪些共享依赖可以外置"等具体决策。运行时侧的"宿主 Import Map、provider 描述符、混合加载器"则属于另一份 ui-plugin-esm-runtime 规范(本仓库归档于 2026-08-07-support-esm-ui-plugins),两者共同构成一次面向 Halo 2.x 的兼容性改造,且新增 ESM 能力对存量 IIFE 产物保持完全兼容。
下面逐条拆解这份规范的核心 Requirements,并结合源码与真实快照做实现级说明。
二、插件与主题 provider 的清单读取与版本阈值
2.1 Plugin provider:默认读取 plugin.yaml,按 spec.requires 自动选型
规范第一条要求(Requirement: Plugin provider compatibility)界定了插件 provider 的兼容性基线:
- 当调用方未传入
manifestPath时,助手默认读取../src/main/resources/plugin.yaml; - 当使用自动格式选择时,如果
plugin.yaml的spec.requires是简单稳定版本或形如>=MAJOR.MINOR.PATCH的范围、且其最小版本 ≥ 2.26.0,则配置为 ESM provider 输出; - 若
spec.requires缺失、非法、为通配符、为复合表达式或允许低于 2.26.0 的稳定版本,则自动回落到 legacy IIFE provider 输出,并对无法解析的范围给出解释性警告; - 当调用方显式选择 IIFE 时,保持既有插件输出、全局名、externals、globals 与产物目录行为完全不变。
这条规则的本质是:不把 Halo 完整的 Java 版本范围文法复制到 Node 侧,只为"选一种输出格式"解析最常用的一小撮语法。>=2.26.0、2.26.0 这类最小稳定版本可直接判定,而 ^、~、|| 复合、* 通配等情况无法可靠判定最低版本,因此宁可警告并退回 IIFE,也不冒险产出与目标宿主不兼容的 ESM 产物。这一点在 design.md 的 "Let the bundler select format" 一节有明确解释。
实际代码里,插件的 viteConfig/rsbuildConfig 助手入口位于 ui/packages/ui-plugin-bundler-kit/src/vite.ts 与 ui/packages/ui-plugin-bundler-kit/src/rsbuild.ts,配置项形状在 ui/packages/ui-plugin-bundler-kit/README.md 中完整定义。以 Vite 为例:
import { viteConfig } from "@halo-dev/ui-plugin-bundler-kit/vite";
export default viteConfig({
// provider 缺省为 "plugin"
format: "auto", // "auto" | "iife" | "esm",默认 "auto"
vite: {
// 自定义 Vite 配置,Vue 插件已内置,不要重复添加
},
});
2.2 Theme provider:默认读取 ../theme.yaml,模块名来自 metadata.name
主题 provider 的清单行为(Requirement: Theme provider manifest)与插件对称:
- 未传
manifestPath时默认读取../theme.yaml; - 传入
manifestPath时改读指定路径(README 中给出manifestPath: "../custom-theme.yaml"的示例); - 清单中
metadata.name为earth时,provider 模块名被配置为theme:earth; - 主题使用自动格式选择时,套用与插件 provider 完全相同 的
spec.requires阈值规则(即 ≥ 2.26.0 选 ESM,否则 IIFE)。
主题前端工程通常位于主题根目录的 ui-plugin/ 子目录下,目录结构形如:
theme-root/
├── theme.yaml
└── ui-plugin/
├── package.json
├── src/index.ts
└── vite.config.ts
对应配置:
import { viteConfig } from "@halo-dev/ui-plugin-bundler-kit/vite";
export default viteConfig({
provider: "theme",
vite: {},
});
2.3 产物资源根:主题的 /themes/{name}/ui-plugin/assets/
主题 provider 的构建输出(Requirement: Theme provider build output)在 Vite 与 Rsbuild 下有一组统一的默认值:
- 生产输出目录均为
dist(README 中"Build Output"一节确认:主题 provider 的开发与生产产物都在dist,Halo 只读取主题包中的ui-plugin/dist/**); - IIFE 输出:Vite 将
base配置为/themes/{metadata.name}/ui-plugin/assets/;Rsbuild 将 public path 配置为同样的/themes/{metadata.name}/ui-plugin/assets/; - ESM 输出:不硬编码资源目录,而是产出可重定位的 provider 相对资源 URL(Vite 侧)或从入口 URL 推导异步资源地址(Rsbuild 侧),这正是主题/插件资源可能被 Halo 从
console旧目录兜底提供服务的前提(详见后文"可重定位输出"); - 无论选哪种格式,都应产出该格式所需的主入口、样式、chunk 与 provider 清单。
IIFE 模式下主题复用与插件 IIFE 完全一致的 legacy 外部依赖与全局变量映射;ESM 模式下主题复用与插件 ESM 相同的宿主运行时快照与外置规则,保证插件/主题在宿主 Import Map 下共享同一份 Vue 系运行时。
2.4 Rsbuild 2 工具链约束:只注入一个版本匹配的 Vue 插件
主题构建场景中规范特别强调:当主题使用 Rsbuild 2 助手时,preset 必须用与 Rsbuild 大版本兼容的 Vue 插件编译主题,且不得在 linked workspace 开发中把 Rsbuild 1 的 preset 插件注入 Rsbuild 2 编译。任务清单 Tasks 20 "Stabilize Rsbuild Theme and Development Watch Builds" 记录了真实的隔离动机:linked bundler kit 若携带与调用方自定义插件版本不匹配的 Vue 插件,会导致真实 Rsbuild 2 主题构建失败;发布侧的 peer contract 负责提供调用方支持的构建工具链,本地 linked checkout 则不能静默回落到旧版自动安装 peer。
三、单一 Vue 编译器插件与 Vue 选项透传
规范中的 "Provider Vue compiler configuration" 要求 Vite 与 Rsbuild 助手各自恰好持有一个 Vue 编译插件,同时允许调用方定制该内置插件,而不必(也不应)自行注册第二个框架插件:
- 调用方通过顶层
vue字段传入的选项,会被交给唯一的@vitejs/plugin-vue(Vite)或@rsbuild/plugin-vue(Rsbuild)实例; - 其他普通调用方插件仍在 provider 默认值之后按正常合并语义合并;
- 不传
vue时保留默认 Vue 编译行为。
这样设计的原因在 design.md 中写得很清楚:Vite/Rsbuild 配置合并可能把两份框架 transform 同时保留下来,因此 Vue 编译插件不能放进嵌套的 caller plugin 列表。README 给出了两个可运行示例:
// Vite:通过顶层 vue 定制 SFC 编译器
import { viteConfig } from "@halo-dev/ui-plugin-bundler-kit/vite";
export default viteConfig({
vue: {
template: {
compilerOptions: {
isCustomElement: (tag) => tag === "halo-app-card",
},
},
},
vite: {},
});
// Rsbuild:同样的意图,改用 vueLoaderOptions
import { rsbuildConfig } from "@halo-dev/ui-plugin-bundler-kit/rsbuild";
export default rsbuildConfig({
vue: {
vueLoaderOptions: {
compilerOptions: {
isCustomElement: (tag) => tag === "halo-app-card",
},
},
},
rsbuild: {},
});
务必保持 @vitejs/plugin-vue / @rsbuild/plugin-vue 不出现在嵌套的 plugins 数组中,否则 SFC transform 会被执行两次。
四、格式选择语义:auto、iife、esm
现代 Vite/Rsbuild 助手统一接受 format: "auto" | "iife" | "esm",默认 auto(Requirement: Provider format selection)。规范为四种组合场景约定了行为:
| 场景 | 行为 |
|---|---|
显式 iife,即使 provider 要求 ≥ 2.26.0 |
照常输出 legacy IIFE,作为迁移逃生舱(migration escape hatch) |
显式 esm,但 spec.requires 允许低于 2.26.0 的版本 |
输出强兼容性警告;绝不改写 provider 清单或静默改选 IIFE |
显式 esm,且 spec.requires 无法推导出受支持的目标版本 |
要求调用方额外提供 targetHaloVersion 构建选项,才能选择宿主运行时快照 |
显式 iife |
完全跳过目标解析,不需要 spec.requires 或目标快照 |
显式 esm + 2.26 prerelease |
校验显式目标快照后放行构建;而 auto 选型始终以稳定版 2.26.0 为阈值 |
其中最重要的原则是:一旦 ESM 与快照被选定,后续任何具体的依赖解析、导入或输出校验失败都只会让构建失败,绝不会把本应产出的 ESM 静默降级成 IIFE。显式强制 ESM 且目标无法从 spec.requires 推导时的写法:
import { viteConfig } from "@halo-dev/ui-plugin-bundler-kit/vite";
export default viteConfig({
format: "esm",
targetHaloVersion: "2.26.0",
vite: {},
});
从源码结构看,格式选择与 ESM 特有逻辑被拆分在 vite-esm.ts、rsbuild-esm.ts 与基础 vite.ts、rsbuild.ts 中,ESM 相关校验集中在共享模块 shared-dependencies.ts 与 provider-manifest.ts。
五、宿主运行时快照与共享依赖校验
5.1 稀疏不可变快照:spec.requires 是唯一兼容声明
规范中的 "Target Halo shared dependency validation" 是构建器与宿主之间的"契约锚点"。Halo 不维护"可接受的 provider 依赖范围"这类手工清单,而是发布稀疏不可变快照:仅在共享版本、根导出或运行时桥发生变化时才发布新快照,多个 Halo 补丁版本可以复用同一份快照。快照记录三件事(见 design.md 与 README):
- 基线 Halo UI 构建实际解析到的精确包名与版本;
- 真实宿主运行时产物中的 package-root 运行时导出;
- 运行时桥与单例需求(
runtime.bridge、runtime.global、runtime.identity)。
本仓库随 bundler kit 携带的第一份快照是 runtime-snapshots/halo-2.26.0.json,通过 runtime-snapshots/index.ts 暴露(文件头注明由 scripts/generate-runtime-snapshot.mjs 生成,勿手工编辑)。其中记录的十个共享根与精确版本示例:
| 共享根 | 快照记录版本(halo-2.26.0) | runtime 关键信息 |
|---|---|---|
vue |
3.5.41 | 桥 vue / 全局 Vue / singleton |
vue-router |
5.1.0 | 全局 VueRouter / singleton |
pinia |
4.0.3 | 全局 Pinia / singleton |
axios |
1.16.1 | 全局 axios / shared |
@formkit/vue |
2.1.2 | 全局 FormKitVue / singleton |
@formkit/core |
2.1.2 | 全局 FormKitCore / singleton |
@halo-dev/ui-shared |
2.26.0 | 全局 HaloUiShared / shared |
@halo-dev/components |
2.26.0 | 全局 HaloComponents / shared |
@halo-dev/api-client |
2.26.0 | 全局 HaloApiClient / shared |
@halo-dev/richtext-editor |
2.26.0 | — |
快照条目形如:
{
"vue": {
"version": "3.5.41",
"exports": ["computed", "defineComponent", "ref", "...(实际浏览器产物暴露的全部静态导出)"],
"runtime": { "bridge": "vue", "global": "Vue", "identity": "singleton" }
}
}
注意快照中的 exports 是从真实浏览器运行时全局产物推导出的静态导出清单,不包括 __esModule 这类合成元数据(除非浏览器产物真的暴露它),因为 ESM 具名导出必须静态可知。任务清单 Tasks 1 "Shared Dependency Contract" 还确认了校验目标:快照恰好暴露上述十个根,且不含 VueUse、Tiptap、ProseMirror 或其他 @formkit/* 子包。
5.2 目标快照选择:最新且不新于目标
当自动格式选择走向 ESM 后,构建器按如下规则选择快照:
- 从
>=MAJOR.MINOR.PATCH(或简单稳定版本)推导最小 Halo 版本,选择基线不新于该目标的最新快照。例如requires: ">=2.26.0"选中 2.26.0 快照;需要 Halo 2.28 才引入的导出,provider 必须抬高最低版本,并用包含对应快照的 bundler-kit 版本构建; - 若安装的 bundler kit 中没有目标版本的精确快照、但存在更旧的合格快照,则复用最新合格旧快照,并警告"目标引入的新导出需要更新 bundler kit";
- 目标为 prerelease 时按其稳定核心版本比较:同一核心版本有精确快照就直接复用,且不得把同核快照描述为"更旧版本";仅当 prerelease 的核心版本比所选快照基线新时才提示正在复用旧快照;
- 若找不到任何兼容基线的 ESM 快照,构建失败并给出诊断:指明目标版本,建议更新 bundler kit 或改选 IIFE 输出。
快照的生成属于维护者的显式动作,不与每次包构建/CI 耦合。仓库 README 给出维护流程:
# 1. 把 bundler-kit 版本设为快照所代表的 Halo 版本
# 2. 生成:由包版本推导输出路径,并捕获实际解析的宿主版本与导出
pnpm --filter @halo-dev/ui-plugin-bundler-kit snapshot:generate
# 3. 发布前核对并与兼容性 fixtures 一起验证
pnpm --filter @halo-dev/ui-plugin-bundler-kit snapshot:check
# 4. 只要还有旧产物可能被加载,就保留旧快照文件
构建永远在本地使用内嵌的不可变历史快照,不会拉取可变在线元数据。
5.3 校验"实际解析到的包",而不是声明范围
规范里最重要的一条实现事实:bundler 校验的是 provider 工程根目录下真实解析出的共享包(用于类型检查与编译的那个实例),package.json 里的 ^3.5.0 声明或 lockfile 文本只作为诊断上下文,不是判定依据。
解析流程锚定在 provider 工程(或清单)路径,而非安装的 bundler-kit 包位置,顺序如下(对应 design.md 与 Tasks 11):
- 用 Node 兼容的工程解析法,从 provider 工程解析该包;
- 即使
package.json未导出,也要定位到持有该包的 package root; - 解析符号链接 / workspace 链接到真实 package root;
- 读取并归一化精确的包名与版本;
- 与 bundler 最终解析出的入口做交叉核对,确保 alias 或条件入口变更无法绕过校验。
对"只暴露 exports map、没有 module/main/根 index.js"的包(exports-only),构建器使用受维护的 Node 兼容解析(README 与 Tasks 11.1 说明引入 pkg-types)读取包元数据,不推断也不需要传统入口文件。当 Rsbuild 因外置而无法暴露最终资源时,同样通过该解析器从导入方解析包并定位最近持有 package.json 的根。
版本差异处理采取"警告代替拒绝"的务实边界(这也是对"本地生态采样表明严格范围拒绝会误伤可用 provider"这一 trade-off 的回应):
- 同大版本且 provider 不新于宿主:不产生任何兼容性提示;
- 同大版本但 provider 更新:输出一条"新于宿主"的最佳努力兼容性提示;
- 大版本不同:输出更强提示,且同一包不会重复出现第二条提示;
- 无论哪种版本漂移,只要根身份与静态导出等具体检查通过,构建都不失败。
5.4 导入校验:语法感知 + 根级白名单
ESM provider 的导入校验采用语法感知的 ESM lexing,因此注释或字符串里长得像 import 的文本不会被误判为运行时依赖(对应 Tasks 21.4 曾把"仅正则发现 import"替换掉,并补充了 import-like 注释/字符串的回归测试)。具体规则:
- 导入共享根:允许构建,导入保持 external,交给宿主 Import Map 解析;
- 导入共享包的子路径(如
vue/dist/vue.esm-bundler.js):构建失败,因为宿主 Import Map 只暴露精确根说明符; - 使用命名空间导入或动态读取共享包命名空间属性:允许,但无法逐属性证明,输出 best-effort 警告;
- 导入
@formkit/core(直接或经打包的 FormKit 子包间接引入):外置到与@formkit/vue相同的宿主图,保证 FormKit Core registry 的单实例身份; - 导入其他
@formkit/*包:打进 provider 产物,但其对@formkit/core的运行时导入仍外置; - 导入 VueUse 等非共享依赖:打进 provider 产物(宿主 Import Map 不提供 VueUse);
- 在同时使用
@halo-dev/richtext-editor时直接导入@tiptap/*或prosemirror-*:保持私有,但输出兼容性警告,提示编辑器类身份与跨版本行为是 best-effort,优先使用 editor 包的 re-export; - 调用方配置把非共享依赖外置:ESM 构建失败并给出诊断,且不会产出含浏览器无法解析的裸 import 的清单。
FormKit 之所以被特殊对待,是因为它的 core registry 是模块实例级状态,若经由第二份 FormKit 副本做桥接就谈不上真正共享;因此 @formkit/vue 与 @formkit/core 都成为公共根,表单身份测试覆盖了 getNode、submitForm、reset 等跨宿主/provider 的操作。
5.5 错误信息与修复建议的"可执行性"
无论哪类具体失败,错误信息都应指出 provider 根、被解析的来源、选中的 Halo 快照与可行的修复方式(IIFE 输出或更新 bundler kit)。成功的 ESM 构建则输出一份对齐的共享依赖版本表;如有版本漂移、命名空间/动态导入、编辑器身份边界等"无法完全验证"的情况,只在校验后输出至多一个确定性兼容性提示块,版本漂移按依赖归组,命名空间与动态导入诊断按共享根归组、按来源去重,并且源码标签使用 provider 相对或依赖相对路径而非绝对文件系统路径。
六、构建诊断:格式决策可见,但不污染运行时清单
Requirement: Provider build diagnostics 划清了"构建元数据"与"运行时清单"的边界:
- 每次构建完成格式选择后,输出中标明所选格式以及它是显式、自动还是"自动 IIFE 回落";
- ESM 构建额外标明目标 Halo 版本、所选快照基线,以及每个共享包在 provider 侧与宿主侧的精确版本;
- 这些目标与版本信息只存在于诊断中,绝不序列化进运行时清单——因为运行时无法独立证明哪个包编译了 bundle,provider 的身份、启用状态、安装版本与兼容性要求本来就该来自 Halo 侧管理元数据,而不是 provider 自持的清单。
七、ESM provider 输出清单 ui-plugin.json
只要 ESM 输出被选中且构建成功,bundler 就在 provider UI 输出根生成最小化清单(Requirement: ESM provider output manifest)。插件输出根为 ui/,主题为 ui-plugin/dist/。清单 schema(见 design.md)极为精简:
{
"format": "esm",
"entry": "./main.js",
"style": "./style.css"
}
要点:
format恒为esm;entry是主入口;style是可选的唯一启动样式表;- 所有路径都是归一化相对路径且必须留在 provider 资源根内;绝对路径、跨域路径或越出 provider 根的路径会被拒绝;
- 属于异步 chunk 的 CSS 不进清单,由 bundler 运行时在对应 JS chunk 加载时按需加载;
- 运行时清单不重复构建目标或已解析共享版本(已在诊断中使用);
- 该文件名
ui-plugin.json被 bundler kit 保留:README 明确要求不要创建、拷贝或产出任何同名文件;没有该文件的产物一律视为 legacy IIFE(即使其spec.requires也满足 2.26+); - 已存在但格式错误的清单被归类为 invalid provider,绝不回退执行可能过期的 IIFE 入口。
ESM 入口本身默认导出既有 PluginModule 契约,并保留对受支持共享根的 import 交由宿主 Import Map 解析;它不做顶层的路由/组件副作用注册。IIFE 输出则不产出 ESM 清单,避免 Halo 误分类。
八、可重定位产物:为何 ESM 输出必须"相对化"
ESM 输出最容易被忽略却最关键的约束是可重定位性。插件的构建目标目录优先是 ui,但 Halo 可能通过 legacy console 兜底目录发现完整 ESM 产物;构建产物事先并不知道后端最终会选中哪个完整资源目录。因此:
- Vite ESM 使用相对 build base,其 module-preload helper 依据
import.meta.url解析依赖; - Rsbuild ESM 使用自动运行时 public path,使异步 CSS 与其他运行时加载资源跟随实际入口位置;
- 产物不得硬编码优先的
ui目录; - provider 既有的构建脚本无需改动其输出拷贝目录。
同理,含动态 import 的 provider 会产出位于 provider 资源根下、可独立寻址的 ESM chunk;入口与 chunk 均使用相对或 provider 根 URL,使插件与主题都能工作。主 CSS 的资产引用则相对其所属样式表解析。
Rsbuild 开发态有一个单独风险:其 dev.assetPrefix 默认基于 dev server base,因此 ESM preset 同时把开发 prefix 设为 auto。这样 rsbuild build --watch --env-mode=development 在首次编译与后续 watch 重编译间保持同一套可重定位资源契约(Tasks 20.3 增加了同时观察首次 watch 构建与源码触发的重构建的集成测试);这只影响 build-watch 输出,不引入跨 Halo 的 HMR。
九、Vite 与 Rsbuild 的 ESM 输出等价性
规范最后一部分(Requirement: Vite and Rsbuild ESM equivalence)要求两个构建体系对插件与主题 ESM provider 呈现外部可观察等价契约:相同的清单 schema、宿主运行时快照、导入限制、入口导出契约、资源 URL 规则与格式选择语义(任务清单 6.2 明确要求 Rsbuild 执行与 Vite 相同的外置与最终输出校验)。具体到生产默认值:
- 生产压缩:Vite 走原生生产构建全量压缩 JS/CSS;Rsbuild 使用 Rspack 原生 module 输出、SWC 压缩 JS、Lightning CSS 压缩;
- 单个启动 JS 入口:都只保留一个含默认
PluginModule导出的启动入口;动态 import 请求的代码独立可寻址;非内联资产使用 provider-root-safe URL; - Vite 的特殊处理:ESM provider 被当作最终浏览器入口而非"保留空白符的库分发产物"——Vite 的 ES library 输出有意保留空白,为得到可部署的最小化产物,bundler 不走 library 模式,而是用普通 Rollup 浏览器入口并保留入口导出签名;
- IIFE 契约不受影响:Vite IIFE library 输出与 Rsbuild IIFE window-library 输出保留既有 globals、文件名、启动 CSS 与 chunk 行为,存量调用方的
viteConfig/rsbuildConfig调用无需任何改动; - 内容哈希不变式:默认 preset 输出的启动入口、启动样式表、异步 JS/CSS 与非内联资产都带内容哈希;若最终输出里出现无内容哈希、且由浏览器直接加载的二级资源,ESM 构建失败并给出可执行诊断;source map、legal-comment 文件与 provider 清单等非运行时 sidecar 不受该不变式约束;调用方 filename 函数只要产出内容哈希运行时资源名即被接受,不要求特定配置形状;
- 覆盖即越权:若 Vite/Rsbuild 特定覆盖会绕过依赖校验或改变最终输出契约(模块输出、IIFE 模式、模块 chunk 格式/加载方式、必需入口文件名、Halo 控制的 externals),对应助手在编译前失败并给出诊断,而不是产出一份具有误导性的 provider 清单。
十、与运行时规范的关系及迁移建议
理解 bundler-provider 规范时应当记住它只是 ESM UI 插件能力的一半。运行时侧(ui-plugin-esm-runtime 规范)定义了 provider 清单发现、宿主运行时快照、Import Map、provider 描述符、@halo-dev/ui-shared 的注册 store 与混合加载器。而宿主永远不依据 spec.requires 推断产物格式:新 bundler 产出的 ui-plugin.json 是 ESM 的唯一信号,缺失即 legacy。也就是说,即使某插件的清单早已声明 requires: ">=2.26.0",只要它还是 IIFE 产物,就继续按 legacy 加载——这保证了旧 bundler 构建的产物在 Halo 2.x 全程可用。
对本仓库读者的迁移建议(对应 proposal.md 的 Impact 与 README 的 Choosing 章节):
- 目标 Halo ≥ 2.26.0 的新 UI 插件/主题:默认用
format: "auto",让 bundler 依据spec.requires自动产出 ESM;需要显式强制时可写format: "esm",必要时配合targetHaloVersion。 - 仍要支持 2.26 之前 Halo 的存量工程:保留 IIFE(显式或自动回落均可),Halo 2.x 会继续加载旧产物。
- 两个 preset 都内置唯一 Vue 编译器,自定义通过顶层
vue字段,不要重复添加框架插件。 - 共享依赖只允许快照中那十个根;VueUse、
@tiptap/*、prosemirror-*、非@formkit/vue/@formkit/core的 FormKit 子包都会被打进产物;Axios 请勿改写共享实例,需要隔离客户端用axios.create()。 - 校验粒度以"真实解析包 + 根导出"为准,版本漂移只警告不拒建;只有具体身份、深导入、缺失导出等可证伪的问题才让 ESM 构建失败,而失败绝不静默降级 IIFE。
更完整的配置示例、路径别名与第三方插件接入方式,可查阅 @halo-dev/ui-plugin-bundler-kit README;相关单测覆盖在 ui-plugin-bundler-kit 测试目录(provider.spec.ts、runtime-snapshot.spec.ts、provider-manifest.spec.ts、shared-dependencies.spec.ts、legacy.spec.ts 等),可作为理解上述每种场景断言行为的第一手素材。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00