# elfui
**Repository Path**: bloom_lmh/elfui
## Basic Information
- **Project Name**: elfui
- **Description**: 一款Web Component框架,支持TS单文件组件,使用类似Vue API语法,无运行时VDOM
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: https://elfui-2igtsk.maozi.io/
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-07-11
- **Last Updated**: 2026-07-29
## Categories & Tags
**Categories**: Uncategorized
**Tags**: TypeScript, Lit, solid, webcomponent, Vue
## README
ElfUI
一款面向原生 Web Components 的编译时细粒度响应式框架。
中文官网 ·
English Docs ·
GitHub
ElfUI 使用普通 TypeScript 文件定义组件,将模板编译为直接 DOM 更新,并输出标准 Custom Elements。它借鉴 Vue 的模板与组合式开发体验、Solid 的细粒度更新思路,以及 Lit 对 Web Components 平台的尊重。
> TypeScript in, Custom Elements out.
## 🧭 环境要求
| 工具 | 版本 |
| ------- | ------------------------- |
| Node.js | `^20.19.0` 或 `>=22.12.0` |
| pnpm | `>=10.28.0`(推荐) |
也可以使用 npm、Yarn 或 Bun。本文示例统一使用 pnpm。
## 🚀 快速开始
推荐使用官方脚手架创建项目并安装依赖:
```bash
pnpm create elfui@beta my-app --install
cd my-app
pnpm dev
```
脚手架会进入交互模式,可选择 TypeScript、Macro 组件、样式方案、Router、测试、代码规范与 CI。需要直接采用推荐配置时:
```bash
pnpm create elfui@beta my-app --default --install
```
建议安装 [ElfUI Language Tools](https://marketplace.visualstudio.com/items?itemName=SWUST-WEBLAB-LMH.elfui-language-features),获得模板高亮、补全、诊断、跳转与格式化支持。
## 🗂️ 仓库结构(Monorepo)
本仓库使用 pnpm workspace,主要目录如下:
| 路径 | 说明 |
| ---------------------------------------------------------- | ------------------------------------------------------- |
| [`packages/core`](packages/core) | 用户主入口(`@elfui/core`) |
| [`packages/reactivity`](packages/reactivity) | 细粒度响应式系统(`@elfui/reactivity`) |
| [`packages/runtime`](packages/runtime) | 组件运行时与 Web Components helpers(`@elfui/runtime`) |
| [`packages/compiler-template`](packages/compiler-template) | HTML 模板解析器(`@elfui/compiler-template`) |
| [`packages/compiler`](packages/compiler) | Macro 组件编译器(`@elfui/compiler`) |
| [`packages/vite-plugin`](packages/vite-plugin) | Vite 编译集成(`@elfui/vite-plugin`) |
| [`packages/shared`](packages/shared) | 内部共享工具(`@elfui/shared`) |
## ✨ 特性
- **TS 文件组件**:不需要 `.vue` 文件,也不强制使用 JSX。
- **编译期模板**:构建时分析模板、生成诊断并输出直接 DOM 操作。
- **细粒度响应式**:每个动态点只订阅自己读取的状态。
- **无 VNode / patch**:状态变化直接更新对应 DOM,不运行虚拟 DOM diff。
- **标准 Web Components**:输出 Custom Elements,可用于原生页面或其他框架。
- **Shadow DOM 边界**:组件内部可隔离,并通过 CSS 变量与 `::part()` 开放样式入口。
- **完整组件能力**:Props、Emits、Model、Slots、生命周期、指令、插件和内置组件。
- **独立生态包**:Router、UI Kit、Language Tools 与 Chain 扩展按需安装。
## 📦 安装
新项目优先使用脚手架。已有 Vite 项目可以手动安装:
```bash
pnpm add @elfui/core@beta
pnpm add -D @elfui/vite-plugin@beta
```
```ts
// vite.config.ts
import { defineConfig } from "vite";
import { elfuiMacroPlugin } from "@elfui/vite-plugin";
export default defineConfig({
plugins: [elfuiMacroPlugin()]
});
```
Router 是独立包,需要单独安装:
```bash
pnpm add @elfui/router@beta
```
## 🧩 第一个组件
```ts
// Counter.ts
import { defineHtml, defineStyle, useRef } from "@elfui/core";
defineStyle(`
:host {
display: inline-block;
}
button {
padding: 8px 12px;
}
`);
const count = useRef(0);
const increment = (): void => count.set(count.peek() + 1);
export default defineHtml(` `);
```
使用 `createApp` 注册并挂载根组件,不需要在 `index.html` 手写自定义元素标签:
```ts
// main.ts
import { createApp } from "@elfui/core";
import Counter from "./Counter";
createApp(Counter).mount("#app");
```
## 🏗️ 组件结构
一个 Macro 组件由普通 TypeScript 顶层逻辑和一个导出的 `defineHtml(\`...\`)` 组成:
| API | 用途 |
| ----------------- | ----------------------------------- |
| `defineProps()` | 声明外部属性与类型 |
| `defineEmits()` | 声明组件事件 |
| `defineModel()` | 声明 `v-model` 双向绑定 |
| `defineSlots()` | 声明插槽契约 |
| `defineOptions()` | 配置 Shadow DOM、表单控件等组件选项 |
| `defineStyle()` | 声明组件样式 |
| `defineExpose()` | 向父组件暴露实例方法 |
| `useComponents()` | 注册当前模板依赖的局部组件 |
| `defineHtml()` | 定义并导出组件模板 |
`defineExpose()` 默认会在开发态提示与宿主原生成员的命名冲突。组件确实需要增强
`focus()`、`blur()` 等原生语义时,应显式声明允许覆盖:
```ts
defineExpose({ focus: () => input.value?.focus() }, { overrideNative: ["focus"] });
```
普通命令方法优先使用不冲突的业务名称,例如 `scrollToOption()`,不要覆盖
`HTMLElement.scrollTo()`。
```ts
import {
defineEmits,
defineHtml,
defineModel,
defineOptions,
defineProps,
defineSlots
} from "@elfui/core";
const props = defineProps<{ label: string }>();
const emit = defineEmits<{ save: [value: string] }>();
const value = defineModel({ default: "" });
defineSlots<{ default: () => unknown }>();
defineOptions({ shadow: "open" });
export const SaveField = defineHtml(`
`);
```
## ⚡ 响应式
`useRef` 用于基本类型或需要整体替换的值,`useReactive` 用于对象、数组与集合:
```ts
import { batch, useComputed, useEffect, useReactive, useRef } from "@elfui/core";
const count = useRef(1);
const user = useReactive({ name: "Elf", online: true });
const doubled = useComputed(() => count.value * 2);
useEffect(() => {
document.title = `${user.name}: ${doubled.value}`;
});
batch(() => {
count.value++;
user.online = false;
});
```
`batch()` 会把同步写入延迟并去重到最外层 batch 结束;编译生成的模板事件会自动建立同样的批处理边界。
| API | 用途 |
| --------------- | --------------------------------------------- |
| `useRef()` | 创建带 `.value`、`.set()` 与 `.peek()` 的 Ref |
| `useReactive()` | 创建深度响应式对象、数组、Map 或 Set |
| `useComputed()` | 创建惰性派生状态 |
| `useEffect()` | 自动收集依赖并执行副作用与清理函数 |
| `watch()` | 精确监听数据源并取得新旧值 |
| `batch()` | 把多次同步写入合并为一次 effect 通知 |
## 🔄 组件生命周期
```ts
import { onMounted, onUnmounted } from "@elfui/core";
onMounted(() => {
console.log("组件已挂载");
});
onUnmounted(() => {
console.log("组件已卸载");
});
```
| 阶段 | Hooks |
| -------- | -------------------------------- |
| 挂载前后 | `onBeforeMount`、`onMounted` |
| 更新前后 | `onBeforeUpdate`、`onUpdated` |
| 卸载前后 | `onBeforeUnmount`、`onUnmounted` |
| 属性变化 | `onAttributeChanged` |
| 缓存激活 | `onActivated`、`onDeactivated` |
| 错误捕获 | `onErrorCaptured` |
## 🔌 外部工具集成
接管 DOM 的工具应在 template ref 就绪后初始化,Observer 直接监听 ref,并在卸载时释放。ECharts 这里只是集成示例,不会打包进 ElfUI:
```ts
import { defineHtml, onMounted, useResizeObserver, useTemplateRef } from "@elfui/core";
import * as echarts from "echarts";
const chartRoot = useTemplateRef("chart");
let chart: echarts.ECharts | undefined;
onMounted(() => {
chart = echarts.init(chartRoot.value!);
chart.setOption({ series: [{ type: "bar", data: [3, 7, 5] }] });
return () => {
chart?.dispose();
chart = undefined;
};
});
useResizeObserver(chartRoot, () => chart?.resize());
export const ChartPanel = defineHtml(``);
```
## 🎨 样式
可以直接传入模板字符串,也可以像脚手架生成的项目一样组合导入的独立样式字符串:
```ts
import { defineStyle } from "@elfui/core";
import styles from "./Button.scss?inline";
defineStyle(`:host { display: block; }`, styles);
```
beta.7 已删除 `html`、`css` tagged-template helper。内联模板字符串应直接传给 `defineHtml()` 和 `defineStyle()`。
Shadow DOM 隔离组件内部样式。组件可以使用 CSS 自定义属性接收主题值,并通过 `part` 开放可控的外部样式入口:
```ts
export const Button = defineHtml(` `);
```
```css
elf-button {
--button-color: #16803c;
}
elf-button::part(control) {
font-weight: 600;
}
```
`:class=${...}` 支持字符串、数组和对象,`:style=${...}` 支持样式对象与 CSS 变量。
## 🧷 插槽 Slot
ElfUI 基于标准 Web Components 插槽模型,支持默认插槽与具名插槽:
```ts
export const Panel = defineHtml(`
`);
```
```html
标题
默认插槽内容
```
需要由父组件消费子组件数据时,可使用 `defineSlots()` 与 `useScopedSlot()` 声明和读取作用域插槽。
## 📝 模板表达式
ElfUI 模板里有三种值来源:
| 值来源 | 写法 | 示例 |
| ---------------------- | ----------- | ------------------------------ |
| 静态 HTML | 普通字符串 | `class="panel"` |
| 外层 TypeScript 作用域 | `${...}` | `${count}`、`@click=${save}` |
| 模板局部作用域 | `{{ ... }}` | `v-for` 中的 `{{ item.name }}` |
```ts
const open = useRef(true);
const items = useReactive([
{ id: 1, name: "Macro" },
{ id: 2, name: "Web Components" }
]);
export const FeatureList = defineHtml(`
`);
```
`${...}` 消费 TypeScript 文件中的值;`{{ ... }}` 只用于 `v-for` 和作用域插槽等由编译器创建的局部变量。
## 🪄 指令
| 指令 | 用途 |
| ------------------------------- | ---------------------- |
| `v-if` / `v-else-if` / `v-else` | 创建或移除条件分支 |
| `v-for` | 渲染带 key 的列表 |
| `v-show` | 切换显示状态但保留 DOM |
| `v-model` | 绑定表单值或组件 Model |
| `v-once` | 只渲染一次 |
| `v-memo` | 按依赖缓存模板区域 |
组件局部指令必须把 `defineDirective()` 赋值给变量;变量名会转换为 kebab-case 模板名:
```ts
const autoFocus = defineDirective({
mounted(element) {
element.focus();
}
});
export const SearchField = defineHtml(``);
```
应用级指令使用 `app.directive()` 注册。同名时组件局部指令优先。
局部指令可以捕获 props、ref、host、常量和辅助函数。捕获的 setup 状态按组件实例隔离;模块安全的定义仍保持静态,不产生额外实例分配。
当前 beta.11 的公开 API 统一保留 `onMounted`、`onUnmounted`、`useComputed`、`useEffect`、`watch`、`theme`、`defineDirective` 和 `app.directive`;旧的 `onMount`、`onUnmount`、`computed`、`watchEffect`、`watchPostEffect`、`watchSyncEffect`、`useTheme` 与进程级 `directive()` 导出已删除。
## 🔔 事件
原生事件使用 `@事件名=${handler}`:
```ts
const submit = (event: SubmitEvent): void => {
event.preventDefault();
};
export const Form = defineHtml(`
`);
```
模板支持 `.stop`、`.prevent`、`.once`、`.capture`、`.passive` 等事件修饰符。组件事件使用 `defineEmits()` 声明,最终以标准 Custom Event 对外派发。单参数直接作为 `detail`,多参数组成数组;默认 `bubbles`、`composed` 和 `cancelable` 都是 `false`。
需要让特定组件事件穿过 Shadow DOM 并允许取消时,可以通过 `defineOptions()` 配置;`emit()` 返回 `dispatchEvent()` 的布尔结果:
```ts
defineOptions({
emitOptions: {
events: {
save: { bubbles: true, composed: true, cancelable: true }
}
}
});
const accepted = emit("save", value);
```
只有确实需要 DOM 传播的事件才建议开启 `bubbles/composed`,避免组件内部事件意外穿透边界。
## 🚦 应用
`createApp()` 创建彼此隔离的应用实例。每个实例拥有独立的配置、插件、全局组件、指令和依赖注入上下文:
```ts
import { createApp } from "@elfui/core";
import App from "./App";
import { Button } from "./Button";
const app = createApp(App, { title: "ElfUI" });
app.component(Button);
app.directive("focus", {
mounted: (element) => (element as HTMLElement).focus()
});
app.provide("apiBase", "/api");
app.config.errorHandler = (error) => console.error(error);
app.mount("#app");
```
同一页面可以创建多个 App;每个 App 只能成功挂载一次,并可通过 `app.unmount()` 卸载。无效 selector、目标容器尚不存在或挂载准备失败时,可以修正原因后在同一 App 上重试 `mount()`。
依赖注入按组件逻辑父链解析,可以跨 open/closed Shadow Root,也会在 `Teleport` 到
`body` 后保留原 Provider 与 App 上下文;嵌套 Provider 以最近者优先。需要动态更新时应
provide 一个 `useRef()` 或其他响应式对象,后注册的子 Custom Element 会读取当前值。
## 🧱 内置组件
| 能力 | 用途 |
| -------------------------------- | ---------------------------------- |
| `Teleport` | 将弹层内容渲染到组件树外的目标节点 |
| `Transition` / `TransitionGroup` | 处理元素和列表的进入、离开动画 |
| `KeepAlive` | 缓存暂时离开的组件实例 |
| `Suspense` | 管理异步内容、fallback 与错误边界 |
## 🧬 组合式函数
| 能力 | 用途 |
| ---------------------------------- | -------------------------------------- |
| `useTemplateRef()` | 类型化访问模板元素或组件实例 |
| `useHostAttr()` / `useHostClass()` | 将响应式状态反射到 Custom Element Host |
| `useExtend()` / `useVariant()` | 扩展基础组件或创建组件变体 |
| `useFormControlContext()` | 编写可参与原生表单的组件 |
完整说明与示例请查看[中文官网](https://elfui-2igtsk.maozi.io/)或[英文官网](https://elfui-docs.vercel.app/en/)。
## 📐 标准组件模式
建议组件目录保持简单:
```text
Button/
├─ index.ts
├─ style.scss
└─ types.ts # 仅在公共类型较多时创建
```
推荐顺序:
1. 导入依赖与样式。
2. 使用 `defineProps`、`defineEmits`、`defineModel` 声明组件契约。
3. 创建响应式状态、计算属性与事件函数。
4. 注册生命周期、Host helpers 和局部组件。
5. 最后导出 `defineHtml(\`...\`)`。
## 🌐 浏览器支持
ElfUI 输出 ES2022 和标准 Custom Elements,需要浏览器支持:
- Custom Elements v1
- Shadow DOM v1
- ES Modules 与 ES2022
建议使用当前仍受支持的 Chrome、Edge、Firefox 与 Safari。更旧的浏览器需要由应用构建工具降级语法并按需提供 Web Components polyfill。
## 🌱 生态
| 项目 | 说明 |
| ------------------------------------------------------------------------ | ---------------------------- |
| [`@elfui/core`](https://www.npmjs.com/package/@elfui/core) | Macro 组件、响应式与应用 API |
| [`@elfui/vite-plugin`](https://www.npmjs.com/package/@elfui/vite-plugin) | Macro 组件编译与模板诊断 |
| [ElfUI Router](https://github.com/bloom-lmh/elfui-router) | 独立路由包 |
| [Create ElfUI](https://github.com/bloom-lmh/create-elfui) | 官方项目与组件脚手架 |
| [ElfUI Kit](https://github.com/bloom-lmh/elfui-kit) | 官方 UI 组件库 |
| [Language Tools](https://github.com/bloom-lmh/elfui-language-tools) | VS Code 插件与语言服务器 |
| [Extensions](https://github.com/bloom-lmh/elfui-extensions) | Chain 等可选扩展 |
| [Documentation](https://github.com/bloom-lmh/elfui-docs) | 指南、API 与生态文档 |
Macro + Vite 是当前推荐主线。Chain 是独立扩展,适合运行时模板、渐进式接入与无构建场景。
## 🛠️ 本地开发
```bash
pnpm install
pnpm verify
pnpm size
pnpm verify:release
pnpm verify:publish
```
`pnpm verify` 会执行边界检查、格式化检查、Lint、类型检查、构建、单元测试和模板类型检查。`pnpm size` 会约束真实 tree-shaken 应用和各聚合入口的体积;`pnpm size:detail` 可按 API 与 workspace 包归因 Core 全量入口。`pnpm verify:release` 还会执行体积、真实 Chromium 相对性能、外部工具、宿主框架、多 Runtime 和发布产物门禁。`pnpm verify:publish` 会从真实 npm tarball 创建临时消费项目,并验证 ESM、SSR import、类型、exports、tree shaking 以及 esbuild、Rollup、Vite 构建。
## 🤝 参与贡献
欢迎提交 Issue 与 Pull Request。提交前请运行 `pnpm verify`,并使用 Conventional Commits 格式编写提交信息。
## 📄 License
[MIT](./LICENSE) © ElfUI contributors