Cline 仓库中的 OpenTUI Solid Reconciler 配置全解:从 create-tui 脚手架到单文件可执行构建
本篇以 Cline 仓库内置的 OpenTUI 技能参考文档 .agents/skills/opentui/references/solid/configuration.md 为主体,系统讲解使用 Solid 版本 OpenTUI(@opentui/solid)搭建终端 UI 项目所需的完整工程配置:create-tui 脚手架与手动搭建、tsconfig/bunfig/package.json 三个关键配置文件、render() 渲染器选项、Bun.build 打包与跨平台可执行文件生成、环境变量与测试配置,以及三类高频配置故障的排查方法。读完后你可以从零创建一个可运行、可测试、可分发的 Solid TUI 应用,并理解每一项配置背后 Solid JSX 编译器的实际工作机制。
OpenTUI Solid 在 Cline 仓库中的位置
Cline 仓库在 .agents/skills/opentui/ 目录下维护了一套面向终端 UI 开发的技能参考体系。其入口 .agents/skills/opentui/SKILL.md 将 OpenTUI 的三层框架划分为 core(命令式 API)、react(React reconciler)与 solid(Solid reconciler),并规定每个框架参考遵循五文件结构:REFERENCE.md(总览)、api.md(运行时 API)、configuration.md(工程配置)、patterns.md(常见模式)、gotchas.md(坑点与调试)。
Solid 框架参考总览给出的选型结论是:当需要最优重渲染性能、基于 signal 的细粒度响应式控制,或构建性能敏感型终端应用时选择 solid/;若团队熟悉 React 则选 @opentui/react,需要最大控制力或构建底层框架/库时则直接使用命令式 @opentui/core。
值得注意的是,Cline 自身的 CLI 应用正是基于同一套 OpenTUI 体系构建的,只是采用了 React 变体。apps/cli/tsconfig.json 中的关键配置:
"jsx": "react-jsx",
"jsxImportSource": "@opentui/react"
而 apps/cli/src/tui/index.tsx 通过 createCliRenderer 创建渲染器并挂载 CLI 界面,其依赖声明见 apps/cli/package.json(@opentui/core 与 @opentui/react 均为 0.4.3)。这印证了本文档所讲的配置模式是 OpenTUI 官方推荐的通用工程范式——React 变体与 Solid 变体在配置思路上同构,仅 JSX 入口包不同。
项目搭建:create-tui 脚手架
快速开始
文档给出的标准启动方式是一行命令:
bunx create-tui@latest -t solid my-app
cd my-app && bun install
使用上的硬性约束与可选项:
- 目标目录
my-app不能已存在,CLI 会替你创建该目录; - 选项
--no-git跳过 git 初始化,--no-install跳过 bun install; - 按 SKILL.md 的 Critical Rules,create-tui 的选项必须放在参数之前:
bunx create-tui -t react my-app合法,bunx create-tui my-app -t react不合法。该文档还特别提示 Agent 场景下应始终使用-t <template>自动模式,避免进入交互式提示(Agent 无法应答交互)。
手动搭建
如果不想用脚手架,手动安装三个核心依赖即可:
mkdir my-tui && cd my-tui
bun init
bun install @opentui/solid @opentui/core solid-js
三者缺一不可:@opentui/core 提供渲染器与所有底层 renderable 原语,solid-js 提供 signal/store 等响应式运行时,@opentui/solid 则是把 Solid 组件树 reconcile 到 OpenTUI renderable 的桥接层,并附带 JSX 编译预加载器与 Bun 构建插件。
TypeScript 配置
tsconfig.json 完整示例
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"jsx": "preserve",
"jsxImportSource": "@opentui/solid",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"types": ["bun-types"]
},
"include": ["src/**/*"]
}
文档标注的三个关键设置及其原理:
jsx: "preserve"—— TypeScript 不转换 JSX,原样保留,交给 Solid 编译器处理。Solid 的响应式模型依赖编译期静态分析:<text>Count: {count()}</text>中的count()调用会在编译后被替换为createComputed依赖跟踪结构,而非运行时虚拟 DOM diff。若此处写成react-jsx或preserve+ 错误 importSource,JSX 会被转成 React 的jsx()调用,运行时报 "React not found"。jsxImportSource: "@opentui/solid"—— 指定 JSX runtime 的导入来源。OpenTUI Solid 在此暴露了自己的 JSX 类型定义与 intrinsics(<text>、<box>、<input>、<tab_select>等),编译器据此把 JSX 元素映射到 OpenTUI renderable,而不是 DOM 元素。module/moduleResolution: "NodeNext"—— 文档推荐用于 OpenTUI 兼容性。Bun 运行时下这保证了 ESM 语义("type": "module"项目)与子路径导出(如@opentui/solid/preload、@opentui/solid/bun-plugin)能被正确解析。
对照 Cline CLI 的 apps/cli/tsconfig.json:它使用 "jsx": "react-jsx" + "jsxImportSource": "@opentui/react",结构完全同构,只是 importSource 换成 React 桥接包。此外 Solid 参考文档中的 gotchas.md 也反复强调:JSX 配置错误(编译成 React 调用)的典型症状是 "React not found" 类报错,修复方式就是把上面两项设置补全。
Bun 配置:bunfig.toml 预加载
这是 Solid 变体与 React 变体在工程配置上差异最大的一点。项目根目录必须有:
preload = ["@opentui/solid/preload"]
该 preload 让 Bun 在加载你的任何代码之前先注册 Solid 的 JSX 变换器,之后每个 .tsx 文件在运行时被 Bun 的 loader 链自动编译。文档将缺失此配置列为头号故障:症状是 SyntaxError: Unexpected token '<' 等 JSX 语法错误、组件不渲染;gotchas.md 中同样将其标记为 "Configuration Issues" 的第一条。
可以对照仓库内的同类用法来理解 preload 机制:apps/vscode/bunfig.toml 也使用了 preload 字段,只不过预加载的是测试桩替换脚本 ./src/test/bun-test-preload.ts——同一个机制,不同的预加载目标。
package.json 配置
文档给出的完整示例:
{
"name": "my-tui-app",
"type": "module",
"scripts": {
"start": "bun run src/index.tsx",
"dev": "bun --watch run src/index.tsx",
"test": "bun test",
"build": "bun run build.ts"
},
"dependencies": {
"@opentui/core": "latest",
"@opentui/solid": "latest",
"solid-js": "latest"
},
"devDependencies": {
"@types/bun": "latest",
"typescript": "latest"
}
}
要点:
"type": "module"配合NodeNext模块解析;dev脚本用bun --watch实现热重载开发循环;start必须走bun run。gotchas.md 明确警告不要用node src/index.tsx或npm run start绕过 Bun,因为 preload 机制由 Bun 运行时提供;- 生产依赖建议锁定版本(示例中的
latest是脚手架起步写法,实际项目应按 Cline CLI 那样钉住具体版本,如0.4.3)。
推荐项目结构与入口代码
文档推荐的结构:
my-tui-app/
├── src/
│ ├── components/
│ │ ├── Header.tsx
│ │ ├── Sidebar.tsx
│ │ └── MainContent.tsx
│ ├── stores/
│ │ └── appStore.ts
│ ├── App.tsx
│ └── index.tsx
├── bunfig.toml # Required!
├── package.json
└── tsconfig.json
bunfig.toml 被单独标注 # Required!,因为如前所述它是 Solid 编译链的开关。
入口文件 src/index.tsx
import { render } from "@opentui/solid"
import { App } from "./App"
render(() => <App />)
render 接受一个返回 JSX 元素的函数(而非元素本身),因为 Solid 需要在自身作用域内建立响应式根;当内部需要创建渲染器时它是异步的,Bun 的 top-level await 让这一调用无需额外包装。
App 组件 src/App.tsx
import { Header } from "./components/Header"
import { Sidebar } from "./components/Sidebar"
import { MainContent } from "./components/MainContent"
export function App() {
return (
<box flexDirection="column" width="100%" height="100%">
<Header />
<box flexDirection="row" flexGrow={1}>
<Sidebar />
<MainContent />
</box>
</box>
)
}
这里体现了 OpenTUI 的 Yoga/Flexbox 布局模型:<box> 是容器 renderable,flexDirection、flexGrow 等属性直接对应 flex 布局——终端窗口就是根容器,占满 100% 宽高后内部再行内分栏。
渲染器配置
render() 选项详解
import { render } from "@opentui/solid"
import { ConsolePosition } from "@opentui/core"
render(() => <App />, {
// Rendering
targetFPS: 60,
// Behavior
exitOnCtrlC: true,
autoFocus: true, // Auto-focus elements on click (default: true)
useMouse: true, // Enable mouse support (default: true)
// Debug console
consoleOptions: {
position: ConsolePosition.BOTTOM,
sizePercent: 30,
startInDebugMode: false,
},
// Cleanup
onDestroy: () => {
// Cleanup code
},
})
各选项的实际影响:
| 选项 | 说明 |
|---|---|
targetFPS |
渲染循环目标帧率,终端 TUI 建议 60;帧率控制重绘节奏,直接影响动画流畅度 |
exitOnCtrlC |
是否捕获 Ctrl+C 触发退出流程 |
autoFocus / useMouse |
两者默认均为 true;前者让点击元素自动获得焦点,后者开启鼠标事件(onMouseDown 等) |
consoleOptions |
OpenTUI 会捕获 console 输出并在界面内展示调试控制台,可配置位置(ConsolePosition.BOTTOM)、占屏比例(sizePercent: 30 即 30%)与是否启动即进入调试模式 |
onDestroy |
渲染器销毁回调,是注册退出清理逻辑的正确位置 |
复用已有渲染器
当渲染器已被外部创建(例如测试环境、嵌入宿主、或需要先配置再挂载)时,直接把 renderer 实例传给 render 的第二个参数:
import { render } from "@opentui/solid"
import { createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer({
exitOnCtrlC: false,
})
render(() => <App />, renderer)
这正是 Cline CLI 的用法:apps/cli/src/tui/index.tsx 先 await createCliRenderer({...}) 再挂载 React 组件树;其单测 apps/cli/src/tui/index.test.ts 中甚至直接 mock 了 createCliRenderer,可见"renderer 与组件树解耦"是框架刻意支持的一等公民能力,Solid 变体完全同理。
构建与分发
普通构建脚本
开发时 preload 在运行时完成 JSX 编译;但打包分发时必须在构建期内完成变换,靠的是 @opentui/solid/bun-plugin:
// build.ts
import solidPlugin from "@opentui/solid/bun-plugin"
await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
target: "bun",
minify: true,
plugins: [solidPlugin],
})
console.log("Build complete!")
运行 bun run build.ts。遗漏该插件的症状是产物中残留未变换的 JSX(gotchas.md 将其列为 Build Without Plugin),产物在非 Bun 预加载环境下必然崩溃。
编译为单文件可执行
利用 compile 选项可以把应用连同 Bun 运行时一起编译成跨平台单文件可执行程序:
import solidPlugin from "@opentui/solid/bun-plugin"
await Bun.build({
entrypoints: ["./src/index.tsx"],
target: "bun",
plugins: [solidPlugin],
compile: {
target: "bun-darwin-arm64", // or bun-linux-x64, etc.
outfile: "my-app",
},
})
文档列出的可用目标:
bun-darwin-arm64— macOS Apple Siliconbun-darwin-x64— macOS Intelbun-linux-x64— Linux x64bun-linux-arm64— Linux ARM64bun-windows-x64— Windows x64
注意 compile 模式下产物是二进制,依赖 Bun 运行时被内联,因此入口文件中的 preload 机制不再参与——这再次说明必须依赖构建期的 solidPlugin 完成 JSX 变换,两者不可互相替代。
环境变量
开发期创建 .env 文件:
# Debug settings
OTUI_SHOW_STATS=false
SHOW_CONSOLE=false
# App settings
API_URL=https://api.example.com
Bun 会自动加载 .env 文件,代码中直接读取:
const apiUrl = process.env.API_URL
前两个变量是 OpenTUI 的调试开关:OTUI_SHOW_STATS 控制帧率/统计信息展示,SHOW_CONSOLE 控制是否显示内置调试控制台——与 render() 的 consoleOptions 形成互补(一个从代码侧、一个从环境侧控制)。
测试配置
测试工具封装
OpenTUI 为测试提供 testRender,文档建议封装一个统一的测试渲染入口:
// src/test-utils.tsx
import { testRender } from "@opentui/solid"
export async function renderForTest(
Component: () => JSX.Element,
options = { width: 80, height: 24 }
) {
return await testRender(Component, options)
}
width/height 指定虚拟终端尺寸(默认 80x24),这是终端 UI 快照测试的基础——所有布局断言都依赖确定的终端几何尺寸。
快照测试示例
// src/components/Counter.test.tsx
import { test, expect } from "bun:test"
import { renderForTest } from "../test-utils"
import { Counter } from "./Counter"
test("Counter renders initial value", async () => {
const { snapshot } = await renderForTest(() => <Counter initialValue={5} />)
expect(snapshot()).toContain("Count: 5")
})
snapshot() 返回当前帧的文本快照,配合 toContain 即可对渲染结果做内容断言。更完整的快照/交互测试方法可参考仓库内 testing/REFERENCE.md。
常见配置问题排查
文档"Common Configuration Issues"一节归纳了三类故障,与 solid/gotchas.md 的 Configuration Issues 章节一一对应:
1. 缺失 bunfig.toml
- 症状:JSX 未被变换,报语法错误(如
SyntaxError: Unexpected token '<') - 原因:Bun 未预加载 Solid 变换器
- 修复:创建
bunfig.toml并写入preload = ["@opentui/solid/preload"]
2. JSX 设置错误
- 症状:JSX 被编译成 React 调用,运行时报 "React not found"
- 原因:
jsx/jsxImportSource指向了 React 而非 OpenTUI Solid - 修复:确认 tsconfig 包含
"jsx": "preserve"与"jsxImportSource": "@opentui/solid"
3. 构建缺少插件
- 症状:打包产物中包含未变换的原始 JSX
- 原因:
Bun.build未挂solidPlugin - 修复:
import solidPlugin from "@opentui/solid/bun-plugin"
await Bun.build({
// ...
plugins: [solidPlugin],
})
三条规则的本质是同一件事:Solid 的 JSX 必须经过 OpenTUI 的专用变换(运行时靠 preload、构建期靠 bun-plugin、类型期靠 jsxImportSource),任何一环缺失都会让 <text>、<box> 这些自定义元素失去正确语义。
小结
@opentui/solid 的工程配置可以浓缩为一张检查清单:
- 脚手架:
bunx create-tui@latest -t solid my-app(选项在前,目录不存在),或手动bun install @opentui/solid @opentui/core solid-js; - tsconfig.json:
jsx: "preserve"+jsxImportSource: "@opentui/solid"+NodeNext模块解析; - bunfig.toml:
preload = ["@opentui/solid/preload"],运行时编译的开关; - package.json:
"type": "module",所有脚本走bun run; - render():按需配置
targetFPS、autoFocus、useMouse、consoleOptions、onDestroy;已有 renderer 实例则作为第二参数传入; - 构建:
Bun.build必须挂@opentui/solid/bun-plugin,跨平台分发用compile.target指定bun-<os>-<arch>; - 测试:
testRender+ 固定终端尺寸 + 文本快照断言。
这套配置体系在 Cline 仓库中并非孤立存在——CLI 应用基于同族的 React 变体(@opentui/react)实现了完全对应的 tsconfig、renderer 创建与 mock 测试实践,可作为跨框架对照的阅读材料。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00