【免费下载链接】hestia-core
A grid-based, modular dashboard built entirely from HTML, CSS, and JS with the ability to create your custom API integrations.
Hestia 是一个纯 HTML、CSS、JavaScript 构建的网格化仪表盘(Dashboard),内置时钟、天气、Glances 监控等组件,也支持接入 Pi-hole、Jellyfin 等家庭服务 API。它最吸引人的设计是:任何人都能用自己的 API 集成能力扩展它——因为一个自定义小组件(Widget)只需继承BaseApp类、调用一次registry.register注册即可上线,全程无需构建工具,也没有后端依赖。
一、为什么 30 分钟就够?先看懂这套组件机制
Hestia 的组件开发只有三步闭环:
- 写类:继承 baseApp.js 导出的
BaseApp,实现render方法返回 HTML 字符串; - 注册:调用 registry.js 单例的
register(type, AppClass, metadata),登记组件类型、默认尺寸和设置表单; - 导入:在 appIndex.js 中加一行
import,组件就会出现在仪表盘的"Add New App"列表里。
整个流程不涉及打包、不涉及路由,所有逻辑都是原生 ES Module。下面拆开这两个核心文件。
二、两大核心:BaseApp 与 Registry
2.1 BaseApp:每个组件必须遵守的"最小契约"
打开 js/apps/baseApp.js,你会发现它只有 10 行代码:
export class BaseApp { constructor() { // Shared logic can go here later } // All apps must implement this async render(appData) { throw new Error("Render method not implemented"); } }它定义了组件生命周期的两个方法:
| 方法 | 是否必须 | 作用 |
|---|---|---|
async render(app) | ✅ 必须实现 | 返回组件卡片的 HTML 字符串,app对象包含该组件的位置、尺寸和用户保存的设置(app.data) |
onMount(el, app) | ⭕ 可选 | HTML 插入 DOM 后执行,适合绑定事件、启动定时器、请求 API |
这种"先渲染静态 HTML,再挂载动态逻辑"的分离设计,是 Hestia 组件模型的关键:render负责长什么样,onMount负责做什么。
2.2 Registry:带样式自动注入的组件注册表
registry.js 导出了一个全局单例registry,核心是register方法:
registry.register('my-app', MyApp, { label: 'My Cool App', // "Add App" 列表里显示的名称 category: 'static', // (可选)分类 defaultSize: { cols: 2, rows: 1 }, // 网格默认占用尺寸 settings: [ /* 设置项数组 */ ], css: `.my-app { ... }` // 组件专属样式,注册时自动注入 <style> });register做了两件关键的事(见 registry.js#L8-L21):
- 把
Class和metadata存入this.apps[type]。注意:如果 type 已存在会直接忽略,不会覆盖,这是防止命名冲突的保护; - 如果元数据里带了
css,立即通过injectStyles注入到页面<head>,且每个组件只注入一次。
注册完成后,网格系统和编辑面板都从注册表取数:
- grid.js 中的
mountAppContent会用registry.get(app.subtype)找到组件类,实例化后依次调用render→ 插入 DOM →onMount; - appEditor.js 用
registry.getAll()渲染"Add New App"弹窗,并根据settings数组自动生成设置表单——你不用手写任何表单 DOM。
三、四步开发你的第一个组件
以官方示例 helloWorld.js 为蓝本,完整流程如下。先获取源码:
git clone https://gitcode.com/gh_mirrors/he/hestia-core第 1 步:创建文件js/apps/myApp.js
第 2 步:继承 BaseApp 实现 render
import { BaseApp } from "./baseApp.js"; import { registry } from "../registry.js"; class MyHello extends BaseApp { async render(app) { const text = app.data.text || "Hello, Hestia!"; return ` <div class="app-content app-type-myhello"> <h2>${text}</h2> </div>`; } }app.data里保存着用户在设置面板填写的所有值(以及卡片背景色、文字色),记得对每个字段提供默认值兜底。
第 3 步:注册组件
registry.register('my-hello', MyHello, { label: 'My Hello', category: 'static', defaultSize: { cols: 2, rows: 1 }, settings: [ { name: 'text', label: 'Greeting Message', type: 'text', placeholder: 'e.g. Hello, World!' } ], css: ` .app-type-myhello { display: flex; align-items: center; justify-content: center; height: 100%; color: inherit; } ` });settings数组支持的type包括:text、select(需配options)、textarea、image-source(图片链接或上传,自动存入 IndexedDB)。每个字段的name就是app.data里的键名,label会原样显示在设置表单中。
第 4 步:在 appIndex.js 中导入
在 js/apps/appIndex.js 末尾加一行:
import './myApp.js';刷新页面,点击工具栏的"+"(Add New App),你的组件就出现在列表里了。添加后卡片会自动寻找空闲网格位置(findEmptySlot逻辑在 grid.js),双击标题还能重命名。
上图正是默认仪表盘里 Image Frame 组件加载 constants.js 中预设素材的渲染效果——它本身就是一个注册在注册表里的组件。
四、进阶:让组件动起来
静态文本只是起点。参考 clockApp.js,在onMount里可以操作 DOM、启动定时器:
onMount(el, app) { const timeEl = el.querySelector('.clock-time'); const update = () => { // 安全检查:卡片被删除后停止运行,避免内存泄漏 if (!el.isConnected) { clearInterval(intervalId); return; } timeEl.innerText = new Date().toLocaleTimeString(); }; update(); const intervalId = setInterval(update, 1000); }两个值得学习的关键点:
el.isConnected检查:用户删除卡片时 DOM 节点会被移除,定时器必须随之停止,否则会出现"幽灵更新";el是整张卡片的元素,用el.querySelector查找内部节点。
如果要接 API(轮询数据、绘制图表),官方 Glances 组件是最好的模板:glancesApp.js 展示了setInterval轮询 + 错误兜底(404/离线提示)+ 按指标切换子模块(逻辑拆在 js/apps/glances/ 目录下)的完整写法。
五、常见坑点清单
| 坑 | 现象 | 解法 |
|---|---|---|
| 忘记在 appIndex.js 导入 | 组件不显示在 Add App 列表 | 加import './myApp.js';,该文件被 main.js 间接加载 |
| type 命名冲突 | 注册被静默忽略 | 打开控制台看[Registry] Registered:日志确认 |
| 忘记写默认值 | 新卡片显示undefined | app.data.xxx一律用|| '默认值'兜底 |
| onMount 里泄漏定时器 | 删除卡片后 CPU 持续占用 | 每次 tick 前检查el.isConnected |
| 样式污染其他组件 | 全局 class 名撞车 | 用.app-type-xxx作用域前缀(CSS 已按组件隔离注入) |
六、关键文件速查
| 文件 | 职责 |
|---|---|
| js/apps/baseApp.js | 组件基类,定义 render 契约 |
| js/registry.js | 注册表单例与样式注入 |
| js/apps/appIndex.js | 组件导入总入口 |
| js/apps/helloWorld.js | 最简完整示例(推荐先读) |
| js/apps/clockApp.js | onMount + 定时器示例 |
| js/apps/glancesApp.js | API 轮询 + 图表 + 错误处理模板 |
| js/grid.js | 网格渲染、组件挂载、空闲位置查找 |
| js/ui/appEditor.js | Add/Edit App 弹窗与设置表单生成 |
| index.html | 页面骨架与工具栏按钮 |
从render返回一段 HTML,到registry.register登记一个类型,Hestia 的 BaseApp + Registry 机制把"组件开发"压缩成了三件事。照着 helloWorld.js 抄一遍、换上自己的数据源,你的第一个自定义小组件 30 分钟就能上线。🚀
【免费下载链接】hestia-core
A grid-based, modular dashboard built entirely from HTML, CSS, and JS with the ability to create your custom API integrations.
相关推荐
用shadcn registry机制搭建你自己的组件注册表:以brainless为例的完整开发者教程
用shadcn registry机制搭建你自己的组件注册表:以brainless为例的完整开发者教程 brainless 是一个把 Claude Code、Co
10 分钟开发你自己的回复 Provider:SightFlow Provider Hub 插件系统完整教程(manifest.json + bundle)
10 分钟开发你自己的回复 Provider:SightFlow Provider Hub 插件系统完整教程(manifest.json + bundle) S
ZCode 插件开发完全指南:10分钟打造你自己的 Skills、命令与 MCP 插件
ZCode 插件开发完全指南:10分钟打造你自己的 Skills、命令与 MCP 插件 ZCode 插件开发 是让 Z.ai 智能编程助手(coding age
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考