From ad36cbb181123c50a0635f442f7f1a26f248dcc3 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Fri, 7 Aug 2026 17:41:29 +0800 Subject: [PATCH 1/2] Document ESM UI providers --- docs/developer-guide/plugin/api-changelog.md | 14 ++ .../plugin/api-reference/ui/api-request.md | 4 +- .../plugin/api-reference/ui/shared.md | 47 +++++ .../developer-guide/plugin/basics/devtools.md | 1 + .../developer-guide/plugin/basics/manifest.md | 2 + .../plugin/basics/structure.md | 7 +- .../developer-guide/plugin/basics/ui/build.md | 160 +++++++++++++++--- .../developer-guide/plugin/basics/ui/entry.md | 2 +- docs/developer-guide/theme/api-changelog.md | 4 + docs/developer-guide/theme/structure.md | 8 +- docs/developer-guide/theme/ui-plugin.md | 101 +++++++++++ sidebars.js | 1 + 12 files changed, 322 insertions(+), 29 deletions(-) create mode 100644 docs/developer-guide/theme/ui-plugin.md diff --git a/docs/developer-guide/plugin/api-changelog.md b/docs/developer-guide/plugin/api-changelog.md index 2b891bc5..c87c1686 100644 --- a/docs/developer-guide/plugin/api-changelog.md +++ b/docs/developer-guide/plugin/api-changelog.md @@ -3,6 +3,20 @@ title: API 变更日志 description: 记录每一个版本的插件 API 变更记录,方便开发者适配 --- +## 2.26.0 + +### UI 扩展支持 ESM 和异步分块 + +从 Halo 2.26.0 开始,插件和已激活主题的 Console / UC UI 扩展可以使用 ESM 构建和加载,并支持异步 JavaScript、CSS 和其他静态资源分块。Halo 2.x 会继续兼容已有的 IIFE 产物,无需为兼容新版本而重新构建旧插件。 + +将 `@halo-dev/ui-plugin-bundler-kit` 升级到 2.26.0 后,`viteConfig` 和 `rsbuildConfig` 默认根据 `plugin.yaml` 的 `spec.requires` 自动选择格式。简单的 `requires: ">=2.26.0"` 会选择 ESM;暂时无法迁移的项目可以显式设置 `format: "iife"`。ESM 构建生成的 `ui-plugin.json`、入口、样式、分块和静态资源必须作为一个完整目录打包。详细文档请参考 [UI 构建](./basics/ui/build.md#output-format)。 + +ESM 插件可以从 Halo 共享运行时导入 Vue、Vue Router、Pinia、Axios、FormKit 和公开的 Halo UI 包,其他依赖默认保留在插件自己的构建产物中。共享包的完整列表和兼容性校验规则请参考 [共享运行时依赖](./basics/ui/build.md#shared-runtime-dependencies)。 + +### 查询 UI provider 的注册状态 + +`@halo-dev/ui-shared@2.26.0` 新增 `stores.uiPlugins()`,用于查询插件或已激活主题的 UI provider 是否被发现、是否已经成功注册以及当前状态。它替代了 `window.PluginName` 和 `window.enabledUiPlugins` 等依赖 IIFE 全局变量的检测方式。详细文档请参考 [共享工具库 > uiPlugins](./api-reference/ui/shared.md#uiplugins)。 + ## 2.25.0 ### 表单定义 > `select` 选项支持图标和描述 diff --git a/docs/developer-guide/plugin/api-reference/ui/api-request.md b/docs/developer-guide/plugin/api-reference/ui/api-request.md index f7d4a724..22ed281d 100644 --- a/docs/developer-guide/plugin/api-reference/ui/api-request.md +++ b/docs/developer-guide/plugin/api-reference/ui/api-request.md @@ -63,7 +63,9 @@ axiosInstance.get("/apis/foo.halo.run/v1alpha1/bar").then(response => { }) ``` -此外,在最新的 `@halo-dev/ui-plugin-bundler-kit@2.17.0` 中,已经排除了 `@halo-dev/api-client`、`axios` 依赖,所以最终产物中的相关依赖会自动使用 Halo 本身提供的依赖,无需关心最终产物大小。 +`@halo-dev/ui-plugin-bundler-kit` 会让插件复用 Halo 提供的 `@halo-dev/api-client` 和 `axios`。旧版 IIFE 通过兼容全局对象提供这些依赖,Halo 2.26.0 开始支持的 ESM 则通过共享运行时模块提供,插件代码都应继续使用标准的包导入。 + +直接从 `axios` 导入的是共享的标准 Axios 模块,不包含 Halo 的认证配置。请勿修改它的全局 defaults 或 interceptors;需要独立配置时使用 `axios.create()`。`@halo-dev/api-client` 导出的 `axiosInstance` 是另一个带有 Halo 认证和统一错误处理的实例,也不应修改它的 defaults 或 interceptors。 :::info[提醒] 如果插件中使用了 `@halo-dev/api-client@2.17.0` 和 `@halo-dev/ui-plugin-bundler-kit@2.17.0`,需要提升 `plugin.yaml` 中的 `spec.requires` 版本为 `>=2.17.0`。 diff --git a/docs/developer-guide/plugin/api-reference/ui/shared.md b/docs/developer-guide/plugin/api-reference/ui/shared.md index 235fb5ad..b6adf4d3 100644 --- a/docs/developer-guide/plugin/api-reference/ui/shared.md +++ b/docs/developer-guide/plugin/api-reference/ui/shared.md @@ -15,6 +15,53 @@ description: 介绍 @halo-dev/ui-shared 包中的共享工具库 pnpm install pinia ``` +### uiPlugins + +从 Halo 2.26.0 开始,可以通过 `stores.uiPlugins()` 查询当前页面发现的插件和已激活主题的 UI provider 状态。使用此 API 时,需要将 `@halo-dev/ui-shared` 和 `@halo-dev/ui-plugin-bundler-kit` 升级到 2.26.0 或更高版本,并将插件的 `spec.requires` 设置为不低于 Halo 2.26.0。 + +```ts +import { stores } from "@halo-dev/ui-shared" +import { computed } from "vue" + +const uiPlugins = stores.uiPlugins() + +// 是否在当前 provider 列表中 +uiPlugins.isEnabled("plugin-search") + +// 当前页面中是否已经成功注册 +const searchRegistered = computed(() => + uiPlugins.isRegistered("plugin-search") +) + +// 读取 Halo 提供的只读状态 +uiPlugins.get("plugin-search") +``` + +主题 provider 使用 `theme:{metadata.name}` 作为名称,例如 `theme:theme-earth`。 + +#### 属性 + +- `registrations`:只读的 provider 注册记录列表。 + +每条记录包含: + +```ts +interface UiPluginRegistration { + name: string + type: "plugin" | "theme" + version: string + status: "pending" | "registered" | "failed" +} +``` + +#### 方法 + +- `get(name)`:返回指定 provider 的只读注册记录,不存在时返回 `undefined`。 +- `isEnabled(name)`:是否在当前页面的 provider 描述中被发现,不代表其 UI 已经注册成功。 +- `isRegistered(name)`:当前页面中是否已经成功完成 UI 注册。 + +该 store 由 Halo 管理,插件不应尝试修改注册记录,也不应依赖 provider 的加载或注册顺序。需要检测其他插件是否启用时,应使用 `isEnabled` 代替 `window.PluginName` 或 `window.enabledUiPlugins`;不支持通过该 store 获取、调用或导入其他 provider 的 `PluginModule`。 + ### currentUser 用于获取当前登录用户的信息,示例: diff --git a/docs/developer-guide/plugin/basics/devtools.md b/docs/developer-guide/plugin/basics/devtools.md index ec456d6e..22386f56 100644 --- a/docs/developer-guide/plugin/basics/devtools.md +++ b/docs/developer-guide/plugin/basics/devtools.md @@ -198,6 +198,7 @@ haloPlugin { // exclude '**/.idea/**' // exclude '**/.git/**' // exclude '**/.gradle/**' + // exclude 'src/main/resources/ui/**' // exclude 'src/main/resources/console/**' // exclude 'build/**' // exclude 'gradle/**' diff --git a/docs/developer-guide/plugin/basics/manifest.md b/docs/developer-guide/plugin/basics/manifest.md index f609abb0..2683243b 100644 --- a/docs/developer-guide/plugin/basics/manifest.md +++ b/docs/developer-guide/plugin/basics/manifest.md @@ -54,6 +54,8 @@ spec: 如果你在 plugin.yaml 中配置了 `settingName` 但确没有对应的 `Setting` 自定义模型资源文件,会导致插件无法启动,原因是 `Setting` 模型 `metadata.name` 为你配置的 `settingName` 的资源无法找到。 ::: +从 `@halo-dev/ui-plugin-bundler-kit@2.26.0` 开始,`spec.requires` 也用于自动选择 UI 构建格式。支持的推导写法和回退行为请参考 [UI 构建 > 输出格式与 Halo 目标](./ui/build.md#output-format)。 + ## 插件运行模式 Halo 插件可以在两种模式下运行:`deployment`(默认)模式和 `development` 开发模式。 diff --git a/docs/developer-guide/plugin/basics/structure.md b/docs/developer-guide/plugin/basics/structure.md index 450c666d..3195528f 100644 --- a/docs/developer-guide/plugin/basics/structure.md +++ b/docs/developer-guide/plugin/basics/structure.md @@ -30,9 +30,6 @@ description: 了解插件项目的文件结构 │ │ └── starter │ │ └── StarterPlugin.java │ └── resources -│ ├── console -│ │ ├── main.js -│ │ └── style.css │ └── plugin.yaml ├── LICENSE ├── README.md @@ -51,10 +48,10 @@ description: 了解插件项目的文件结构 - `StarterPlugin.java`:插件后端的入口文件,位于 `src/main/java/com/example/starter` 路径下。你可以根据需要修改包名和类名,但需要确保该类继承 `run.halo.app.plugin.BasePlugin`,以指定其为插件的入口。 - `plugin.yaml`:这是插件的描述文件,位于 `src/main/resources` 目录下。该文件是必须的,包含插件的基本信息,如插件名称、版本、作者、描述以及依赖等内容。 -- `resources/console`:该文件夹通常包含前端部分打包后生成的文件,包括 main.js(JavaScript 文件)和 style.css(样式表)。如果插件不包含前端部分,此目录可以忽略。 +- `resources/ui`:插件 JAR 中的推荐 UI 资源目录。Gradle 会将 `ui/build/dist` 的完整构建产物复制到 `build/resources/main/ui` 后打包,其中可能包含 `ui-plugin.json`、入口、样式、异步分块和其他静态资源。如果插件不包含 UI 部分,此目录可以忽略。 :::warning[注意] -从 2.11 开始,Halo 支持了 UC 个人中心,且个人中心和 Console 的插件机制共享,所以为了避免歧义,`resources/console` 在后续版本会被重命名为 `resources/ui`,但同时也会兼容 `resources/console`。 +从 2.11 开始,Halo 支持了 UC 个人中心,且个人中心和 Console 的插件机制共享,因此推荐使用 `resources/ui`。Halo 2.x 仍兼容旧项目使用的 `resources/console`,并优先读取 `ui`。 ::: ### 前端部分 diff --git a/docs/developer-guide/plugin/basics/ui/build.md b/docs/developer-guide/plugin/basics/ui/build.md index e30bb591..0c79853f 100644 --- a/docs/developer-guide/plugin/basics/ui/build.md +++ b/docs/developer-guide/plugin/basics/ui/build.md @@ -7,9 +7,12 @@ description: UI 部分的构建说明 ## 原理 -Halo 插件的 UI 部分(Console / UC)的实现方式其实很简单,本质上就是构建一个结构固定的大对象,交给 Halo 去解析,其中包括全局注册的组件、路由定义、扩展点等。在 [halo-dev/create-halo-plugin](https://github.com/halo-dev/create-halo-plugin) 工具创建的项目中,我们使用 `index.ts` 作为入口文件,并在构建之后将 `main.js` 和 `style.css` 放到插件项目的 `src/main/resources/console` 目录中,后续 Halo 在内部会自动合并所有插件的 `main.js` 和 `style.css` 文件,并生成最终的 `bundle.js` 和 `bundle.css` 文件,然后在 Console 和 UC 中加载这两个资源并解析。 +Halo 插件的 UI 部分(Console / UC)以 `index.ts` 为源码入口,默认导出一个包含组件、路由和扩展点等内容的 `PluginModule`。Halo 2.x 支持两种构建和加载格式: -所以本质上,我们只需要使用支持将 `index.ts` 编译为 `main.js` 和 `style.css` 的工具,然后输出到插件项目的 `src/main/resources/console` 目录中即可,在 [halo-dev/create-halo-plugin](https://github.com/halo-dev/create-halo-plugin) 的模板中可以看到,我们提供了一个名为 `@halo-dev/ui-plugin-bundler-kit` 的库,这个库包含了 [Vite](https://vite.dev/) 和 [Rsbuild](https://rsbuild.dev/) 的预配置,插件项目只需要通过简单的配置即可使用。 +- **ESM**:从 Halo 2.26.0 开始支持。每个插件独立加载入口、样式和异步分块,可以使用标准的动态 `import()` 和独立缓存。 +- **IIFE**:旧版兼容格式。Halo 会将插件和当前主题的 `main.js`、`style.css` 汇总后加载,现有产物在 Halo 2.x 中无需重新构建。 + +在 [halo-dev/create-halo-plugin](https://github.com/halo-dev/create-halo-plugin) 创建的项目中,`@halo-dev/ui-plugin-bundler-kit` 提供了 [Vite](https://vite.dev/) 和 [Rsbuild](https://rsbuild.dev/) 的预配置。它会根据插件清单选择输出格式、生成 Halo 所需的元数据,并将共享依赖和资源路径配置为与 Halo 运行时兼容的形式,因此不建议自行实现这部分构建协议。 ## @halo-dev/ui-plugin-bundler-kit @@ -28,7 +31,7 @@ Halo 插件的 UI 部分(Console / UC)的实现方式其实很简单,本 安装依赖: ```bash -pnpm install @halo-dev/ui-plugin-bundler-kit@2.22.0 vite -D +pnpm install @halo-dev/ui-plugin-bundler-kit@2.26.0 vite -D ``` 创建 vite.config.ts: @@ -114,7 +117,7 @@ Rsbuild 是基于 Rspack 开发的上层构建工具,其优势在于兼容 Web 安装依赖: ```bash -pnpm install @halo-dev/ui-plugin-bundler-kit@2.22.0 @rsbuild/core -D +pnpm install @halo-dev/ui-plugin-bundler-kit@2.26.0 @rsbuild/core -D ``` 创建 rsbuild.config.ts: @@ -187,18 +190,133 @@ export default rsbuildConfig({ }); ``` -### HaloUIPluginBundlerKit +### 配置内置的 Vue 编译器 + +`viteConfig` 和 `rsbuildConfig` 已经分别创建了一个 Vue 插件实例。需要修改 Vue 模板编译选项时,应使用顶层的 `vue` 字段,不要在内部的 `plugins` 数组中再次添加 `@vitejs/plugin-vue` 或 `@rsbuild/plugin-vue`。 + +Vite: + +```ts +export default viteConfig({ + vue: { + template: { + compilerOptions: { + isCustomElement: (tag) => tag === "halo-app-card", + }, + }, + }, + vite: {}, +}); +``` + +Rsbuild: + +```ts +export default rsbuildConfig({ + vue: { + vueLoaderOptions: { + compilerOptions: { + isCustomElement: (tag) => tag === "halo-app-card", + }, + }, + }, + rsbuild: {}, +}); +``` + +## 输出格式与 Halo 目标{#output-format} + +`viteConfig` 和 `rsbuildConfig` 使用相同的格式选项: + +```ts +export default viteConfig({ + format: "auto", // "auto" | "iife" | "esm" + vite: {}, +}); +``` + +`auto` 是默认值。构建工具只会从以下两种 `spec.requires` 写法推导 ESM 目标: + +- 稳定版本,例如 `2.26.0`。 +- 简单的最低版本,例如 `>=2.26.0`。 + +推导出的最低版本为 2.26.0 或更高版本时输出 ESM,低于 2.26.0 时输出 IIFE。通配符、组合范围或其他无法直接推导最低稳定版本的写法会产生警告并回退到 IIFE,例如 `^2.26.0`、`>=2.26.0 <3.0.0`。这些写法仍可能是有效的 Halo 版本范围,只是不能用于自动选择 ESM。 + +如果需要暂时保留旧格式,可以显式设置: + +```ts +export default viteConfig({ + format: "iife", + vite: {}, +}); +``` + +只有在无法从 `spec.requires` 推导目标且确实需要 ESM 时,才设置 `targetHaloVersion`: + +```ts +export default viteConfig({ + format: "esm", + targetHaloVersion: "2.26.0", + vite: {}, +}); +``` + +`targetHaloVersion` 只用于选择构建时的 Halo 共享运行时快照,不会改变插件的安装兼容范围。发布 ESM 产物时,仍应确保 `plugin.yaml` 的 `spec.requires` 不允许安装到不支持 ESM 的 Halo 版本。 + +### ESM 构建产物 + +成功的 ESM 构建会额外生成保留文件 `ui-plugin.json`,并可能包含异步 JavaScript、CSS 和其他静态资源: + +```text +build/dist/ +├── ui-plugin.json +├── main.js +├── style.css # 可选的启动样式 +├── chunks/ # 可选的异步 JavaScript 分块 +└── assets/ # 可选的异步 CSS、图片和字体等资源 +``` + +`ui-plugin.json` 由 bundler kit 生成,请勿手动创建、复制或覆盖。打包插件时需要保留完整输出目录,不能只复制 `main.js` 和 `style.css`。没有该文件的产物会继续按旧版 IIFE 格式加载,即使 `spec.requires` 已经包含 Halo 2.26.0。 + +ESM 模式下,bundler kit 会管理入口文件名、模块输出、共享依赖和异步资源的内容哈希。覆盖 Vite/Rsbuild 的相关配置会导致构建失败;如果旧项目依赖这些自定义配置,可以先选择 `format: "iife"`。 + +## 共享运行时依赖{#shared-runtime-dependencies} + +ESM 插件可以从 Halo 运行时导入以下包根路径: + +- `vue` +- `vue-router` +- `pinia` +- `axios` +- `@formkit/vue` +- `@formkit/core` +- `@halo-dev/ui-shared` +- `@halo-dev/components` +- `@halo-dev/api-client` +- `@halo-dev/richtext-editor` + +bundler kit 会校验插件项目实际解析到的包、使用的根导出以及目标 Halo 快照。共享包缺失、使用别名或 fork、从共享包进行深层导入,或者使用目标 Halo 不存在的静态导出时,ESM 构建会失败;仅版本不同通常会产生兼容性警告。未列出的依赖默认打入插件自己的产物,不应添加为外部依赖。 + +`axios` 是 Halo 提供的标准共享模块,请勿修改它的全局 defaults 或 interceptors;需要隔离配置时使用 `axios.create()`。`@halo-dev/api-client` 导出的 `axiosInstance` 是另一个带 Halo 认证和错误处理的实例,详细说明请参考 [API 请求](../../api-reference/ui/api-request.md)。 + +## 加载和生命周期 + +ESM 入口仍需默认导出已有的 `PluginModule`,不应通过顶层副作用自行注册路由或组件。Halo 会并行加载各 provider 的启动样式、ESM 入口和旧版 IIFE bundle,再按照稳定的 provider 顺序注册成功加载的模块。插件不能依赖其他 provider 的求值或注册顺序,也不支持直接导入其他 provider 的实现。 + +单个 provider 的入口、样式或注册失败时,Halo 会跳过该 provider,并继续启动核心 UI 和其他 provider。入口求值产生的定时器、事件监听器等任意副作用无法保证回滚,因此插件安装、升级、启用、禁用或重新加载后,完整刷新 Console 或 UC 页面是受支持的模块替换和恢复边界。 + +## HaloUIPluginBundlerKit(已过时) -旧版本 [plugin-starter](https://github.com/halo-dev/plugin-starter) 使用的方式,目前已经不再推荐。 +旧版本 [plugin-starter](https://github.com/halo-dev/plugin-starter) 使用的方式,目前已经不再推荐,也不支持 ESM 或主题 UI provider。 ## 构建输出 在 `viteConfig` 和 `rsbuildConfig` 中,已经配置好了开发环境和生产构建的输出目录,分别是: -- **开发环境**:`build/resources/main/console`,在开发 UI 的过程中,可以使用 `pnpm dev` 来实时查看效果 +- **开发环境**:目标 Halo 为 2.25.0 或更高版本时输出到 `build/resources/main/ui`;旧目标继续输出到 `build/resources/main/console`。在开发 UI 的过程中,可以使用 `pnpm dev` 实时构建 - **生产环境**:`ui/build/dist` -> 需要注意的是,生产构建的目录仅仅是临时目录,最终在使用 Gradle 构建插件时会自动构建 UI 并复制到 `src/main/resources/console` 目录中。 +> 生产目录是临时构建产物。使用 Gradle 构建插件时,应将完整目录复制到 `build/resources/main/ui`,并随插件 JAR 一起打包。`console` 目录仍作为旧项目的兼容回退。 ## Vite vs Rsbuild{#vite-vs-rsbuild} @@ -206,7 +324,7 @@ Vite 和 Rsbuild 都是优秀的构建工具,但它们在不同的使用场景 ### 何时使用 Rsbuild -- ✅ **代码分割支持** - Rsbuild 为代码分割和懒加载提供了优秀的支持 +- ✅ **代码分割支持** - ESM 模式支持通过动态 `import()` 拆分和按需加载代码 - ✅ **更好的性能** - 对于复杂应用,通常有更快的构建时间和更小的包体积 - ✅ **动态导入** - 非常适合有重度前端组件的插件 @@ -246,7 +364,7 @@ export default definePlugin({ | 特性 | Vite | Rsbuild | | ---------- | ------ | -------- | -| 代码分割 | ❌ 有限 | ✅ 优秀 | +| 代码分割 | ✅ ESM | ✅ ESM | | Vue 生态 | ✅ 优秀 | ✅ 良好 | | 构建性能 | ✅ 良好 | ✅ 优秀 | | 开发体验 | ✅ 优秀 | ✅ 优秀 | @@ -259,10 +377,10 @@ export default definePlugin({ 如果你当前的插件使用的是旧版本的 [plugin-starter](https://github.com/halo-dev/plugin-starter),并且想使用新的 `viteConfig` 和 `rsbuildConfig`,可以参考以下步骤: -1. 更新 `@halo-dev/ui-plugin-bundler-kit` 至 `2.22.0` 或更高版本 +1. 更新 `@halo-dev/ui-plugin-bundler-kit` 至 `2.26.0` 或更高版本 ```bash - pnpm install @halo-dev/ui-plugin-bundler-kit@2.22.0 -D + pnpm install @halo-dev/ui-plugin-bundler-kit@2.26.0 -D ``` 2. 更新 `vite.config.ts` 文件 @@ -295,7 +413,7 @@ export default definePlugin({ } dependencies { - implementation platform('run.halo.tools.platform:plugin:2.22.0') + implementation platform('run.halo.tools.platform:plugin:2.26.0') compileOnly 'run.halo.app:api' testImplementation 'run.halo.app:api' @@ -320,7 +438,7 @@ export default definePlugin({ tasks.register('processUiResources', Copy) { from project(':ui').layout.buildDirectory.dir('dist') - into layout.buildDirectory.dir('resources/main/console') + into layout.buildDirectory.dir('resources/main/ui') dependsOn project(':ui').tasks.named('assemble') shouldRunAfter tasks.named('processResources') } @@ -330,7 +448,7 @@ export default definePlugin({ } halo { - version = '2.22' + version = '2.26' } ``` @@ -372,17 +490,17 @@ export default definePlugin({ } ``` -进行此变更的主要目的是保证 UI 构建的产物不直接输出到源码目录的 resources 目录中,而是通过 Gradle 构建插件时复制到 `src/main/resources/console` 目录中。 +进行此变更的主要目的是保证 UI 构建产物不直接输出到源码目录,而是通过 Gradle 复制到 `build/resources/main/ui` 并打包到插件 JAR 中。 -如果你不想使用新的 Gradle 构建配置,也可以修改 viteConfig 或 rsbuildConfig 的输出目录,和旧版本保持一致: +如果你不想使用新的 Gradle 构建配置,也可以让 viteConfig 或 rsbuildConfig 直接输出到源码资源目录: viteConfig: ```js import { viteConfig } from "@halo-dev/ui-plugin-bundler-kit"; -const OUT_DIR_PROD = "../src/main/resources/console"; -const OUT_DIR_DEV = "../build/resources/main/console"; +const OUT_DIR_PROD = "../src/main/resources/ui"; +const OUT_DIR_DEV = "../build/resources/main/ui"; export default viteConfig({ vite: ({ mode }) => { @@ -403,8 +521,8 @@ rsbuildConfig: ```js import { rsbuildConfig } from "@halo-dev/ui-plugin-bundler-kit"; -const OUT_DIR_PROD = "../src/main/resources/console"; -const OUT_DIR_DEV = "../build/resources/main/console"; +const OUT_DIR_PROD = "../src/main/resources/ui"; +const OUT_DIR_DEV = "../build/resources/main/ui"; export default rsbuildConfig({ rsbuild: ({ envMode }) => { diff --git a/docs/developer-guide/plugin/basics/ui/entry.md b/docs/developer-guide/plugin/basics/ui/entry.md index a224c4cf..936bfd9f 100644 --- a/docs/developer-guide/plugin/basics/ui/entry.md +++ b/docs/developer-guide/plugin/basics/ui/entry.md @@ -3,7 +3,7 @@ title: 入口文件 description: UI 扩展部分的入口文件 --- -入口文件即 Halo 核心会加载的文件,所有插件有且只有一个入口文件,构建之后会放置在插件项目的 `src/resources/console` 下,名为 `main.js`。 +入口文件用于定义 Halo 核心需要加载的 `PluginModule`,每个插件有且只有一个源码入口。使用 `@halo-dev/ui-plugin-bundler-kit` 构建时,IIFE 和 ESM 都会生成一个主入口;ESM 还可以包含异步 JavaScript、CSS 和其他静态资源分块。构建和打包方式请参考 [构建](./build.md)。 为了方便开发者,我们已经在 [halo-dev/create-halo-plugin](https://github.com/halo-dev/create-halo-plugin) 配置好了基础项目结构,包括构建配置,后续文档也会以此为准。 diff --git a/docs/developer-guide/theme/api-changelog.md b/docs/developer-guide/theme/api-changelog.md index 297343ee..c2bb61e2 100644 --- a/docs/developer-guide/theme/api-changelog.md +++ b/docs/developer-guide/theme/api-changelog.md @@ -5,6 +5,10 @@ description: 记录每一个版本的主题 API 变更记录,方便开发者 ## 2.26.0 +### 主题支持提供 ESM UI 扩展 + +主题可以继续复用插件的 `PluginModule` 契约扩展 Console 和 UC。从 Halo 2.26.0 开始,主题 UI provider 支持 ESM、异步 JavaScript 和 CSS 分块,并与插件使用相同的共享运行时依赖和失败隔离机制;已有 IIFE 主题 UI 产物继续兼容。详细文档请参考 [UI 扩展](../../developer-guide/theme/ui-plugin.md)。 + ### 主题目录结构 > 新增页面布局契约 在 2.26.0 中,主题可以通过 `templates/layout.html` 提供 `html(head, content)` 片段,让插件前台页面复用当前主题的页面外壳。Halo 会在主题安装、更新或重载后检查这个模板,并通过 `Theme.status.pageLayout` 暴露 `SUPPORTED`、`MISSING` 或 `INVALID` 状态;未适配或校验异常时,使用布局契约的插件页面会回退到 Halo 内置布局。详细文档可查阅:[页面布局契约](../../developer-guide/theme/page-layout.md)。 diff --git a/docs/developer-guide/theme/structure.md b/docs/developer-guide/theme/structure.md index 22587880..1b2a7a6a 100644 --- a/docs/developer-guide/theme/structure.md +++ b/docs/developer-guide/theme/structure.md @@ -24,7 +24,12 @@ my-theme │ └── archives.html ├── screenshot.png ├── theme.yaml -└── settings.yaml +├── settings.yaml +└── ui-plugin/ # 可选的 Console / UC UI 扩展 + ├── package.json + ├── src/ + │ └── index.ts + └── dist/ # Halo 只读取此构建目录 ``` 详细说明: @@ -35,3 +40,4 @@ my-theme 4. `/screenshot.png` - 可选的主题预览图文件,支持 `screenshot.png`、`screenshot.jpeg`、`screenshot.jpg` 和 `screenshot.webp`。Halo 会按此顺序识别第一个可读文件,用于 Console 主题预览,并通过 `Theme.status.screenshot` 暴露访问地址。 5. `/theme.yaml` - 主题配置文件,配置主题的基本信息,如主题名称、版本、作者等。详细文档请查阅 [配置文件](./config)。 6. `/settings.yaml` - 主题设置定义文件,配置主题的设置项表单。详细文档请查阅 [设置选项](./settings)。 +7. `/ui-plugin/` - 可选的 Console / UC UI 扩展项目。Halo 只读取其中的 `dist` 构建目录,详细文档请查阅 [UI 扩展](./ui-plugin.md)。 diff --git a/docs/developer-guide/theme/ui-plugin.md b/docs/developer-guide/theme/ui-plugin.md new file mode 100644 index 00000000..ca7cfe56 --- /dev/null +++ b/docs/developer-guide/theme/ui-plugin.md @@ -0,0 +1,101 @@ +--- +title: UI 扩展 +description: 通过主题扩展 Console 和 UC 界面 +--- + +除了提供站点前台模板,主题还可以复用插件的 `PluginModule` 契约,为 Console 控制台和 UC 个人中心提供页面、组件和扩展点。只有当前激活主题的 UI provider 会被加载。 + +从 Halo 2.26.0 开始,主题 UI provider 可以使用 ESM 构建,并支持异步 JavaScript、CSS 和其他静态资源分块。Halo 2.x 仍兼容已有的 IIFE 主题 UI 产物。 + +## 目录结构 + +将 UI 项目放在主题根目录的 `ui-plugin` 目录中: + +```text +theme-root/ +├── templates/ +├── theme.yaml +└── ui-plugin/ + ├── package.json + ├── src/ + │ └── index.ts + ├── vite.config.ts + └── dist/ # 构建产物 + ├── ui-plugin.json # ESM 构建生成 + ├── main.js + ├── style.css # 可选 + ├── chunks/ # 可选 + └── assets/ # 可选 +``` + +Halo 只会从主题包的 `ui-plugin/dist` 目录读取 UI provider 资源。发布主题时必须保留完整的 `dist`,不能只复制入口和主样式。 + +## 入口文件 + +入口文件与插件 UI 使用相同的 `PluginModule` 类型,并默认导出 `definePlugin` 的结果: + +```ts title="ui-plugin/src/index.ts" +import { definePlugin } from "@halo-dev/ui-shared" + +export default definePlugin({ + components: {}, + routes: [], + ucRoutes: [], + extensionPoints: {}, +}) +``` + +可用字段、路由和扩展点请参考 [插件 UI 入口文件](../plugin/basics/ui/entry.md)。 + +## 使用 Vite 构建 + +安装依赖: + +```bash +pnpm install @halo-dev/ui-plugin-bundler-kit@2.26.0 vite -D +``` + +创建构建配置: + +```ts title="ui-plugin/vite.config.ts" +import { viteConfig } from "@halo-dev/ui-plugin-bundler-kit" + +export default viteConfig({ + provider: "theme", + vite: {}, +}) +``` + +## 使用 Rsbuild 构建 + +安装依赖: + +```bash +pnpm install @halo-dev/ui-plugin-bundler-kit@2.26.0 @rsbuild/core -D +``` + +创建构建配置: + +```ts title="ui-plugin/rsbuild.config.ts" +import { rsbuildConfig } from "@halo-dev/ui-plugin-bundler-kit" + +export default rsbuildConfig({ + provider: "theme", + rsbuild: {}, +}) +``` + +主题 provider 默认读取上一级目录的 `theme.yaml`,输出到当前 UI 项目的 `dist`,并使用 `/themes/{metadata.name}/ui-plugin/assets/` 作为资源路径。需要使用其他清单路径时,可以通过顶层的 `manifestPath` 配置。 + +## 输出格式和共享依赖 + +`format` 默认为 `auto`。当 `theme.yaml` 使用简单的稳定版本或最低版本要求,并且目标为 Halo 2.26.0 或更高版本时,构建工具会输出 ESM: + +```yaml title="theme.yaml" +spec: + requires: ">=2.26.0" +``` + +如果需要暂时保留 IIFE,可以在 Vite 或 Rsbuild 配置的顶层设置 `format: "iife"`。自动格式选择、`targetHaloVersion`、`ui-plugin.json` 和共享依赖的完整规则与插件相同,请参考 [插件 UI 构建](../plugin/basics/ui/build.md#output-format)。 + +Halo 会把主题 provider 注册为 `theme:{metadata.name}`。例如主题名称为 `theme-earth` 时,可以通过 `stores.uiPlugins().get("theme:theme-earth")` 查询其状态。主题安装、升级、重载或切换后,需要完整刷新 Console 或 UC 页面以加载新的模块图。 diff --git a/sidebars.js b/sidebars.js index a5e426aa..7348e7b2 100644 --- a/sidebars.js +++ b/sidebars.js @@ -392,6 +392,7 @@ module.exports = { "developer-guide/theme/prepare", "developer-guide/theme/config", "developer-guide/theme/structure", + "developer-guide/theme/ui-plugin", "developer-guide/theme/page-layout", "developer-guide/theme/static-resources", "developer-guide/theme/settings", From 08c589ec1fe8cf89bc6787cd328b017d299709cf Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Sat, 8 Aug 2026 00:36:51 +0800 Subject: [PATCH 2/2] Update ESM UI provider documentation --- docs/developer-guide/plugin/api-changelog.md | 4 ++-- docs/developer-guide/plugin/basics/ui/build.md | 16 ++++++++-------- docs/developer-guide/theme/ui-plugin.md | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/developer-guide/plugin/api-changelog.md b/docs/developer-guide/plugin/api-changelog.md index c87c1686..1f4b2b24 100644 --- a/docs/developer-guide/plugin/api-changelog.md +++ b/docs/developer-guide/plugin/api-changelog.md @@ -9,9 +9,9 @@ description: 记录每一个版本的插件 API 变更记录,方便开发者 从 Halo 2.26.0 开始,插件和已激活主题的 Console / UC UI 扩展可以使用 ESM 构建和加载,并支持异步 JavaScript、CSS 和其他静态资源分块。Halo 2.x 会继续兼容已有的 IIFE 产物,无需为兼容新版本而重新构建旧插件。 -将 `@halo-dev/ui-plugin-bundler-kit` 升级到 2.26.0 后,`viteConfig` 和 `rsbuildConfig` 默认根据 `plugin.yaml` 的 `spec.requires` 自动选择格式。简单的 `requires: ">=2.26.0"` 会选择 ESM;暂时无法迁移的项目可以显式设置 `format: "iife"`。ESM 构建生成的 `ui-plugin.json`、入口、样式、分块和静态资源必须作为一个完整目录打包。详细文档请参考 [UI 构建](./basics/ui/build.md#output-format)。 +将 `@halo-dev/ui-plugin-bundler-kit` 升级到 2.26.0 后,`viteConfig` 和 `rsbuildConfig` 默认根据 `plugin.yaml` 的 `spec.requires` 自动选择格式。简单的 `requires: ">=2.26.0"` 会选择 ESM;暂时无法迁移的项目可以显式设置 `format: "iife"`。默认 ESM preset 会为入口、启动样式和异步资源使用内容哈希文件名,`ui-plugin.json` 会记录实际启动资源路径;清单、入口、样式、分块和静态资源必须作为一个完整目录打包。详细文档请参考 [UI 构建](./basics/ui/build.md#output-format)。 -ESM 插件可以从 Halo 共享运行时导入 Vue、Vue Router、Pinia、Axios、FormKit 和公开的 Halo UI 包,其他依赖默认保留在插件自己的构建产物中。共享包的完整列表和兼容性校验规则请参考 [共享运行时依赖](./basics/ui/build.md#shared-runtime-dependencies)。 +ESM 插件可以从 Halo 共享运行时导入 Vue、Vue Router、Pinia、Axios、FormKit 和公开的 Halo UI 包,其他依赖默认保留在插件自己的构建产物中。共享包的完整列表、兼容性诊断和自定义配置边界请参考 [共享运行时依赖](./basics/ui/build.md#shared-runtime-dependencies)。 ### 查询 UI provider 的注册状态 diff --git a/docs/developer-guide/plugin/basics/ui/build.md b/docs/developer-guide/plugin/basics/ui/build.md index 0c79853f..b08334dc 100644 --- a/docs/developer-guide/plugin/basics/ui/build.md +++ b/docs/developer-guide/plugin/basics/ui/build.md @@ -265,20 +265,20 @@ export default viteConfig({ ### ESM 构建产物 -成功的 ESM 构建会额外生成保留文件 `ui-plugin.json`,并可能包含异步 JavaScript、CSS 和其他静态资源: +成功的 ESM 构建会额外生成保留文件 `ui-plugin.json`,并可能包含异步 JavaScript、CSS 和其他静态资源。以下目录仅作示意,实际入口和启动样式路径以 `ui-plugin.json` 为准: ```text build/dist/ ├── ui-plugin.json -├── main.js -├── style.css # 可选的启动样式 -├── chunks/ # 可选的异步 JavaScript 分块 -└── assets/ # 可选的异步 CSS、图片和字体等资源 +├── main..js # 默认 ESM 入口 +├── style..css # 可选,路径由构建工具决定 +├── chunks/ # 可选的异步 JavaScript / CSS 分块 +└── assets/ # 可选的图片、字体等资源 ``` -`ui-plugin.json` 由 bundler kit 生成,请勿手动创建、复制或覆盖。打包插件时需要保留完整输出目录,不能只复制 `main.js` 和 `style.css`。没有该文件的产物会继续按旧版 IIFE 格式加载,即使 `spec.requires` 已经包含 Halo 2.26.0。 +`ui-plugin.json` 由 bundler kit 生成,请勿手动创建、复制或覆盖。打包插件时需要保留完整输出目录,不能只复制清单中记录的入口和启动样式。没有该文件的产物会继续按旧版 IIFE 格式加载,即使 `spec.requires` 已经包含 Halo 2.26.0。 -ESM 模式下,bundler kit 会管理入口文件名、模块输出、共享依赖和异步资源的内容哈希。覆盖 Vite/Rsbuild 的相关配置会导致构建失败;如果旧项目依赖这些自定义配置,可以先选择 `format: "iife"`。 +默认的 ESM preset 会配置模块输出、共享依赖、相对资源路径,并为入口、启动样式和异步资源使用内容哈希文件名。原生 Vite / Rsbuild 配置会在这些默认值之后合并,bundler kit 不会检查、拒绝或重写冲突的覆盖项。如果自定义配置修改了输出格式、入口、资源路径、externals 或文件名,开发者需要自行保证清单一致性、Import Map 兼容性、共享依赖身份、资源迁移和缓存安全。旧项目暂时无法满足这些要求时,可以显式选择 `format: "iife"`。 ## 共享运行时依赖{#shared-runtime-dependencies} @@ -295,7 +295,7 @@ ESM 插件可以从 Halo 运行时导入以下包根路径: - `@halo-dev/api-client` - `@halo-dev/richtext-editor` -bundler kit 会校验插件项目实际解析到的包、使用的根导出以及目标 Halo 快照。共享包缺失、使用别名或 fork、从共享包进行深层导入,或者使用目标 Halo 不存在的静态导出时,ESM 构建会失败;仅版本不同通常会产生兼容性警告。未列出的依赖默认打入插件自己的产物,不应添加为外部依赖。 +bundler kit 会发现对共享包根路径的导入,并在能够读取包元数据时,对比插件安装的版本与目标 Halo 快照中的版本。插件版本更新或主版本不同时会产生尽力而为的兼容性提示,但版本差异不会导致构建失败。bundler kit 不检查静态导出、别名、fork 或构建工具最终解析到的包;从共享包进行深层导入仍会失败,因为 Halo 的 Import Map 只公开上述包根路径。默认 preset 会将未列出的依赖打入插件产物;如果通过自定义配置修改 externals 或依赖解析,开发者需要自行保证浏览器能够解析最终产物。 `axios` 是 Halo 提供的标准共享模块,请勿修改它的全局 defaults 或 interceptors;需要隔离配置时使用 `axios.create()`。`@halo-dev/api-client` 导出的 `axiosInstance` 是另一个带 Halo 认证和错误处理的实例,详细说明请参考 [API 请求](../../api-reference/ui/api-request.md)。 diff --git a/docs/developer-guide/theme/ui-plugin.md b/docs/developer-guide/theme/ui-plugin.md index ca7cfe56..6d573f47 100644 --- a/docs/developer-guide/theme/ui-plugin.md +++ b/docs/developer-guide/theme/ui-plugin.md @@ -3,7 +3,7 @@ title: UI 扩展 description: 通过主题扩展 Console 和 UC 界面 --- -除了提供站点前台模板,主题还可以复用插件的 `PluginModule` 契约,为 Console 控制台和 UC 个人中心提供页面、组件和扩展点。只有当前激活主题的 UI provider 会被加载。 +除了提供站点前台模板,主题还可以复用插件的 `PluginModule` 契约,为 Console 控制台和 UC 个人中心提供页面、组件和扩展点。只有当前激活且版本要求与 Halo 兼容的主题 UI provider 会被加载。 从 Halo 2.26.0 开始,主题 UI provider 可以使用 ESM 构建,并支持异步 JavaScript、CSS 和其他静态资源分块。Halo 2.x 仍兼容已有的 IIFE 主题 UI 产物。 @@ -22,8 +22,8 @@ theme-root/ ├── vite.config.ts └── dist/ # 构建产物 ├── ui-plugin.json # ESM 构建生成 - ├── main.js - ├── style.css # 可选 + ├── main..js # 默认 ESM 入口 + ├── style..css # 可选,路径由构建工具决定 ├── chunks/ # 可选 └── assets/ # 可选 ``` @@ -96,6 +96,6 @@ spec: requires: ">=2.26.0" ``` -如果需要暂时保留 IIFE,可以在 Vite 或 Rsbuild 配置的顶层设置 `format: "iife"`。自动格式选择、`targetHaloVersion`、`ui-plugin.json` 和共享依赖的完整规则与插件相同,请参考 [插件 UI 构建](../plugin/basics/ui/build.md#output-format)。 +如果需要暂时保留 IIFE,可以在 Vite 或 Rsbuild 配置的顶层设置 `format: "iife"`。自动格式选择、`targetHaloVersion`、`ui-plugin.json` 和共享依赖的完整规则与插件相同,请参考 [插件 UI 构建](../plugin/basics/ui/build.md#output-format)。这些默认保证不适用于覆盖输出格式、资源路径、externals 或文件名的原生 Vite / Rsbuild 配置;自定义最终产物的兼容性和缓存安全由主题开发者负责。 Halo 会把主题 provider 注册为 `theme:{metadata.name}`。例如主题名称为 `theme-earth` 时,可以通过 `stores.uiPlugins().get("theme:theme-earth")` 查询其状态。主题安装、升级、重载或切换后,需要完整刷新 Console 或 UC 页面以加载新的模块图。