TermDOM:用 DOM 和 CSS 写终端界面,连 Prism 高亮都能原样跑

终端
TUI
DOM
CSS
前端工具
Node.js
2026/9/28
·

阅读时间: 大约 9 分钟

TermDOM:用 DOM 和 CSS 写终端界面,连 Prism 高亮都能原样跑

TermDOM 渲染出的 Klondike 纸牌游戏(官方演示 GIF)

写终端界面(TUI)的传统套路是:选一个库,学它的 Box、List、Screen、Text 组件,再记住一堆该库特有的布局和事件 API——每个库都像一套新框架。TermDOM 走了一条完全不同的路:它不发明组件库,而是在终端里实现一套真正的 DOM 和 CSSOM。你写 document.createElement("div")、给它加 style、append 到 body,它就用 ANSI 转义序列把这个”网页”画到字符单元格上。本文基于其官方站点做一次拆解。

一、出发点:TUI 为什么非要学一套新 API

Ink、blessed 这类 TUI 库之所以各有各的 API,是因为它们把终端当成一个字符画布,自己造了一套组件模型。代价是:前端工程师已经会的 flexbox、CSS 级联、事件冒泡、媒体查询统统用不上,还得重新学一套;而浏览器生态里的库(语法高亮、状态管理、组件框架)也接不进来。

TermDOM 的赌注是:终端本质上也是一个”矩形网格 + 样式”的渲染目标,既然浏览器已经把 DOM/CSS 规范打磨了三十年,那直接在终端里合规地实现一份 DOM/CSSOM,前端那套技能和库就能零改动搬过来。

二、核心模型:画真实 DOM 节点,变更自动重绘

官方对它的定义很直白:一个把 HTML 和 CSS 渲染到终端的 JavaScript/TypeScript 库。它把真实的 DOM 节点画到终端输出,并在节点被突变(mutation)时重绘屏幕。和浏览器一样,没有 render() 调用——你改了 DOM,画面自动更新:

import {TermDOM} from "@b9g/termdom";
const term = new TermDOM();
term.attach();
const {document} = term;

const heading = document.createElement("div");
heading.style.backgroundColor = "blue";
heading.style.color = "white";
heading.style.padding = "0 1ch";
heading.textContent = "Hello, terminal";

document.body.appendChild(heading);

这和写浏览器前端几乎没区别。包名是 @b9g/termdom,要求 Node.js >= 20。

三、样式:真级联,不是模拟

TermDOM 把你的样式表和内联样式跑过一遍真正的级联,再把计算样式写成 ANSI 转义序列送上屏。颜色对着终端调色板解析;文本装饰画成终端属性:粗体、斜体、下划线、删除线。

它支持的 CSS 能力相当”浏览器化”:

  • 选择器级联:p { color: gray }、.warm { color: yellow }、.warm.hot { color: red; font-weight: bold };
  • 内联样式优先级最高;
  • var() 自定义属性、:has() 状态选择器;
  • @media (max-width: 72ch) 媒体查询——终端一 resize,样式规则重新回答。

官方演示里甚至直接写了一个 <style> 块放进 document.head,然后用 document.body.innerHTML 塞 HTML 字符串。

四、布局:flexbox/grid 跑在字符单元格上

这是它最硬核的部分。TermDOM 用浏览器的布局算法——flexbox、grid、表格、box 模型——在一个字符单元格网格上排版。它把单元格(cell)作为 CSS 长度的基准:1px 和 1ch 都等于一个字符格。文字在盒子边缘自动换行,终端 resize 时重新回流。

官方示例里用 display: flex、flexDirection: row/column、justifyContent: space-between、gap、flex: 1、flexShrink: 0、whiteSpace: nowrap 搭出了一个带头部、侧边栏、主内容区、底部三栏特性卡片的完整 TUI 布局——这些都是前端工程师熟到肌肉记忆的属性。

五、事件:stdin 转义序列 → DOM 事件

输入方向上,TermDOM 把 stdin 的转义序列解码成 DOM 事件,并派发到真实目标:聚焦元素上的 keydown、鼠标指针下元素的 click、带粘贴文本的 paste。Tab 键移动焦点,:focus 样式随之切换。官方做了一个登录表单 demo:三个 input 框,监听每个字段的 input 事件实时预览,监听 keydown 判断回车提交——事件模型和网页表单一致。输入框和下拉选择是作为 UA 阴影树(shadow tree)实现的。

六、最大红利:浏览器库原样跑

实现真 DOM 的回报是:浏览器库不经修改就能在终端里用。官方给了一个叫 Prism 的演示:直接 import Prism from "prismjs" 再加载各语言语法包——Prism 把源码标记成带类名的 token 元素(keyword、string 等各成一类),再套一个 Tomorrow Night 主题 CSS,TermDOM 负责把这套标记排版并画成单元格。也就是说,一个 Web 高亮库和一份 Web 样式表,原封不动地在终端里做了多语言代码高亮(还支持方向键切语言)。

官方同时称它”做少量配置后”能配合大多数前端框架使用。这意味着 React/Vue 那套组件心智理论上可以映射到终端——不过具体适配成本官方没有展开。

七、客观分析:优势与边界

优势:

  1. 心智成本为零:会写网页前端就会写 TermDOM,flexbox/级联/事件/媒体查询全是已知知识;
  2. 生态可复用:Prism 这类库零改动接入,长期看能把 Web 前端生态”下沉”到终端;
  3. 真响应式:终端 resize 触发回流与媒体查询,不是固定画布;
  4. 声明式、自动重绘:告别手动 render() 和脏区计算,mutate 即更新。

边界与存疑:

  1. 版本极早期:官方示例里版本号还是 0.1.x,API 随时可能变;
  2. 性能待验证:把完整 CSSOM/flexbox 引擎跑在字符网格上,高频重绘(如动画、实时日志)的开销与终端刷新帧率如何,官方未给基准;
  3. 终端能力天花板:颜色、字体、图片受终端模拟器限制,画不出浏览器那样的富媒体;鼠标事件在不同终端/SSH 下支持不一;
  4. “配合前端框架”只是承诺:React/Vue 适配需要额外 setup,官方未给出成熟范例,今天的现实仍是”原生 DOM + TS”;
  5. Windows 终端兼容性:ANSI 转义序列在 Windows Terminal vs 旧 conhost 上的表现差异需要实测。

八、谁该关注

  • 每天和命令行打交道、想要一个比 shell 脚本好看、但又不想学新组件库的前端工程师;
  • 想做交互式 CLI 工具、配置向导、TUI 仪表盘,且已经熟悉 CSS 布局的人;
  • 对”DOM 无处不在”这种理念感兴趣、想做技术预研的人。

如果你的 TUI 需求只是几个选择菜单和进度条,现成的 Ink/clack 可能更轻;但当你发现自己在用 flexbox 的脑子却被逼着学一套盒子 API 时,TermDOM 代表了一种更”正本清源”的思路——让终端终于像一个真正的渲染面。

参考来源