tuivuetypescriptframeworkcliterminalrendering type: entity 创建: 2026-05-06 更新: 2026-05-06

vue-termui

Terminal User Interfaces powered by Vue.js. 构建 TUI 的 Vue 3 框架,用 Flexbox 布局 + Yoga 布局引擎将 Vue 组件渲染到终端。

仓库: vue-terminal/vue-termui | npm: vue-termui | License: MIT


架构概览

┌─────────────────────────────────────────┐
│     Vite Dev Server + HMR (WebSocket)   │
│     vite-plugin-vue-termui               │
└────────────────┬────────────────────────┘
                 │ 终端渲染(单文件构建)

┌─────────────────────────────────────────┐
│  packages/core — vue-termui 核心库       │
│  ├── app/createApp.ts  — TuiApp 创建    │
│  ├── renderer/         — Yoga+Vue 渲染  │
│  ├── focus/            — FocusManager   │
│  ├── input/            — 键盘/鼠标处理 │
│  ├── composables/      — Vue Hooks      │
│  └── hmr/client.ts     — WS 热更新客户端│
└─────────────────────────────────────────┘

monorepo packages

Package用途
packages/core核心库(npm 发布为 vue-termui
packages/vite-plugin-vue-termuiVite 插件 + dev server HMR
packages/cliCLI 工具
packages/playground交互式 demo
packages/xterm-playgroundxterm 集成 demo
packages/domino游戏 demo
packages/create-vue-termui项目脚手架
packages/docs文档站

核心模块解析

1. 渲染引擎(packages/core/src/renderer/)

三层渲染架构:

Vue Component Tree

DOMElement (packages/core/src/renderer/dom.ts)
  - TuiNode / DOMElement / TextNode / CommentNode
  - 每个节点持有 yogaNode (Yoga.Layout)

Yoga Layout 计算(yoga-layout-prebuilt)
  - Flexbox 布局
  - setWidth / calculateLayout

Output(packages/core/src/renderer/Output.ts)
  - 虚拟输出缓冲
  - 按 x/y 坐标写入字符
  - ansi-escapes 样式处理

process.stdout / Writable Stream

关键文件:

  • renderer/dom.ts — DOMElement/TextNode 实现,自定义 Virtual DOM 节点
  • renderer/render.tsrenderRoot():Yog a Layout 计算 + Output 生成
  • renderer/Output.ts — 虚拟输出缓冲,按坐标写入,ANSI 样式应用
  • renderer/LogUpdate.ts — 屏幕增量更新(ansiEscapes.eraseLines
  • renderer/nodeOpts.ts — Vue Renderer 接口实现(createElement/insert/patchProp 等)
  • renderer/renderNodeToOutput.ts — 将 DOM 树递归写入 Output
  • renderer/renderBorders.ts — 边框渲染

渲染流程(renderRoot):

node.yogaNode.setWidth(terminalWidth)
node.yogaNode.calculateLayout() // Yoga flexbox 计算
output = new Output({ width, height })
renderNodeToOutput(node, output, { skipStaticElements: true })
// staticNode 用于不变内容(背景/边框)分离渲染

2. TuiApp 创建(packages/core/src/app/createApp.ts)

createApp(rootComponent, options) 返回 TuiApp:

createApp(App, {
  stdout: process.stdout,  // 默认
  stdin: process.stdin,   // 默认
  swapScreens: false,     // 全屏切换
}).mount({
  renderOnce: false,
  exitOnCtrlC: true,
})
// app.waitUntilExit() — 阻塞直到 exit

mount 流程:

  1. cliCursor.hide() — 隐藏光标
  2. SAVE_SCREEN_BUFFER — 全屏模式可选保存屏幕
  3. 创建 DOMElement('tui:root') 作为挂载点
  4. createFocusManager(rootEl) — 初始化焦点管理
  5. attachInputHandler(app, stdin, { setRawMode }) — 注入键盘/鼠标处理
  6. mount(rootEl) — Vue 渲染器挂载

Raw Mode 管理:

  • stdin.setRawMode(true) — 启用终端原始模式
  • ACTIVATE_MOUSE = '\x1b[?1000h\x1b[?1002h\x1b[?1005\x1b[?1006h' — 启用鼠标追踪(1000/1002/1005/1006 模式)
  • 引用计数:rawModeEnableCount 支持嵌套

信号处理:

  • onExit() — 监听 SIGINT/SIGTERM,优雅退出
  • TuiError — 封装退出码和信号

WebSocket HMR(DEV 模式):

if (__DEV__) {
  import('../hmr').then(({ setupHMRSocket }) => {
    setupHMRSocket(newApp, stopApp)
  })
}
// 监听 ws://localhost:3000,接收 crash/restart 消息

3. FocusManager(packages/core/src/focus/FocusManager.ts)

焦点管理系统,支持 Tab/Shift+Tab 焦点导航:

// Focusable 注册
focusManager._add(focusable)
focusManager._remove(focusable)

// 焦点操作
focusManager.focus(id)         // 聚焦指定元素
focusManager.focusNext()       // Tab 下一个
focusManager.focusPrevious()   // Shift+Tab 上一个
focusManager.trapFocus()       // 焦点陷阱(TODO)

// 焦点状态
activeElement: ShallowRef<Focusable | null>

焦点树遍历: 使用 nextDeepSibling / previousDeepSibling 做深度优先遍历,跳过非 focusable 元素,支持 cyclic 循环。

4. 输入处理(packages/core/src/input/handling.ts)

attachInputHandler 将 stdin 数据流分发给注册的 Vue 组件:

stdin.on('data', handleOnData)

parseInputSequence(input) / SPECIAL_INPUT_KEY_TABLE

Event 分发:
  isKeyDataEvent → keyEventMap.get(key).forEach(handler)
  isMouseDataEvent → mouseEventMap.get(type).forEach(handler)
  所有事件 → inputEventSet.forEach(handler)

注入的 Symbols:

  • KeyEventMapSymbolMap<string, Set<KeyDataHandler>>
  • MouseEventMapSymbolMap<MouseEventType, Set<MouseDataHandler>>
  • InputEventSetSymbolSet<InputDataHandler>

5. Composables(packages/core/src/composables/)

Composable功能
onKeyData(key, handler)键盘事件监听
onMouseData(type, handler)鼠标事件监听
onInputData(handler)原始输入监听
onResize(handler)终端尺寸变化
useStdoutDimensions()获取终端宽高
useTitle(title)设置终端标题
useLog()获取 log 实例(用于日志输出)
useInterval(fn, ms)定时器
useTimeout(fn, ms)延时器
useFocus(id)焦点状态
useFocusManager()FocusManager 实例
useStdout()stdout stream 访问

6. Vite 插件(packages/vite-plugin-vue-termui/src/index.ts)

三个插件组合:

  1. AutoImport — 自动导入 vue-termui 的组合式 API(onKeyData / onMouseData / useLog 等)
  2. Components — 自动解析 <TuiBox> / <TuiText> 等组件
  3. Vue — 模板编译器配置:
    • isNativeTag — 只有 tui:* 标签是原生标签(其他按自定义元素处理)
    • isVoidTag<hr> 是自闭合
    • whitespace: 'condense' — 压缩空白

Rollup 配置:

  • target: 'node14' — 面向 Node.js 环境打包
  • external — 外部化所有运行时依赖(vue / yoga-layout / chalk 等)
  • 单文件输出(manualChunks: undefined

7. 组件库(packages/core/src/components/)

组件对应标签功能
TuiBox<div> / <box>Flexbox 布局容器
TuiText<span> / <text>文本节点,支持 ANSI 样式
TuiNewline<Br> / <br>换行
TuiLink<a> / <link>链接
TuiInput<input>文本输入框
TuiTextTransform<transform>文本变换
TuiProgressBar<progressbar>进度条
TuiApp<TuiApp>根组件

样式系统: Yoga Layout 属性(width / height / flexDirection / justifyContent / alignItems 等)+ ANSI 颜色/样式。

8. HMR(packages/core/src/hmr/)

  • client.ts — WebSocket 客户端,监听 server restart/crash 事件,调用 exitApp()
  • server.ts — dev server 使用(已注释为 ”// this should be used by the dev server”)
  • messages.ts — JSON 消息格式定义({ type: 'crash' | 'restart', payload }
  • common.ts — 共享常量(WSS_PORT = process.env.PORT || 3000

用于 Agent-TUI 的可行性分析

✅ 可用部分

1. 渲染引擎(完整可用)

  • Vue 3 响应式 → ANSI 终端渲染的完整管线
  • Flexbox 布局(Yoga)天然支持复杂 UI 结构
  • 增量渲染(LogUpdate)适合动态内容
  • 边框系统(8 种 borderStyle)开箱即用

2. 输入处理(可复用)

  • onKeyData / onMouseData composables 完整可用
  • 鼠标追踪(点击坐标)已实现
  • Raw mode 管理成熟

3. 焦点管理(可扩展)

  • FocusManager 支持 Tab 导航
  • 需要为 Agent-TUI 扩展为可聚焦的”可交互元素”(按钮/输入框/列表项)

4. Dev HMR(参考价值)

  • WebSocket HMR 机制值得借鉴
  • 但 Agent-TUI 通常不需要 HMR,更需要 production 的稳定性

⚠️ 不足部分(需要自行补充)

1. Agent Loop 无关

  • vue-termui 只做 UI 渲染,不包含任何 LLM 调用、工具执行、Session 管理
  • 这些需要另外实现,vue-termui 只能作为”最后一公里”的渲染层

2. 流式输出

  • 当前渲染模式是整树重渲染(renderRoot
  • Agent-TUI 需要增量流式输出(逐字显示 LLM 响应)
  • 需要新增 TuiStreamingText 组件或修改 Output 渲染管线

3. 多区域 UI

  • Agent-TUI 通常需要:消息区 + 工具输出区 + 状态栏 + 输入区
  • 需要实现 多Pane布局(Split/VSplit 组件)
  • 当前只有单根渲染模式,多窗口/多区域需要自己实现

4. 键盘快捷键绑定

  • 当前只有 onKeyData 监听,需要一个快捷键系统Ctrl+C / Ctrl+Z / Ctrl+L 等)
  • 可以基于 onKeyData 包装一个 useShortcut() composable

5. 选择/高亮系统

  • 终端选择文本(鼠标选择范围)没有实现
  • Agent-TUI 可能需要类似”选择列表项”的能力
  • 可以基于鼠标坐标 + Yoga Layout 计算来实现

6. 异步状态

  • 当前 useInterval / useTimeout 是基础定时器
  • Agent-TUI 需要轮询/流式更新的组合模式(如 SSE 事件 → 更新 UI)

❌ 明确不适合的部分

1. Web/HTTP 能力

  • 完全无网络能力,纯 Node.js stdout/stdin
  • Agent 需要的 HTTP 调用、文件操作需要另外实现

2. 滚动视图

  • 没有 ScrollView / Viewport 组件
  • 长输出需要自己实现虚拟滚动

3. 国际化/字体测量

  • string-width 处理 ANSI 宽度,但复杂 Unicode 字形可能不准

推荐集成方案

Agent Core (你自己实现)
  ├── Agent Loop (LLM 调用、工具执行)
  ├── Session 管理
  └── 状态管理(Vue reactive/ref)

vue-termui
  ├── TuiBox / TuiText / TuiInput 等组件(UI 渲染)
  ├── onKeyData / onMouseData(输入捕获)
  └── FocusManager(焦点导航)

process.stdout / process.stdin(终端 I/O)

需要自行补充的模块:

  1. StreamingText 组件 — 逐字追加文本,不整树重渲染
  2. SplitPane / VBox — 多区域布局(消息/输出/状态)
  3. useShortcut() — 全局快捷键绑定
  4. useScrollViewport() — 长内容虚拟滚动
  5. AsyncQueue — 异步任务队列 + UI 状态联动

对比同类方案

| 维度 | vue-termui | Ink | Bubble Tea (Go) | |------|------------|-----------|-------------| | 语言 | TypeScript/Vue | JavaScript/React | Go | | 渲染模型 | Vue + Yoga Layout | React + yoga-layout | Bubble Tea 库 | | 生态 | 小众(活跃维护中)| npm 最大 TUI 生态 | 最成熟 | | 学习曲线 | Vue 开发者友好 | React 开发者友好 | 需学 Go | | Agent 适配 | UI 层可用,逻辑需自建 | 同左 | 同左 | | 流式输出 | 需自行扩展 | 需自行扩展 | 需自行扩展 |

结论: vue-termui 的渲染层可以复用,但 Agent 的核心逻辑(LLM Loop、工具调用、Session 管理)需要另外实现。它和 Ink 在定位上非常相似——都是 UI 渲染库,不是 Agent 框架。


相关 Wiki 条目