wterm:来自 Vercel、基于 Ghostty 构建的 Web 终端

BBetter Stack
Computing/SoftwareInternet Technology

Transcript

00:00:00这是 Wterm,由 Vasell 开发的一款网页终端模拟器
00:00:03它直接渲染到 DOM,而不是使用 canvas。
00:00:06因此文本选择、浏览器搜索、
00:00:08以及屏幕阅读器都能直接兼容。
00:00:10它由 Zig 编写,编译成 12KB 的 WASM 二进制文件,
00:00:14此外还有一个由 LibGhosty 驱动的可选后端,
00:00:17即为 Ghosty 终端提供支持的同一引擎,
00:00:19这让它在浏览器中拥有完全的终端兼容性。
00:00:22但是,一个 12KB 的 WASM 二进制文件
00:00:24真的足以替代原生终端模拟器吗?
00:00:28大概不行,但请点击订阅,我们来一探究竟。
00:00:33网页终端如今几乎随处可见。
00:00:36在 GitHub Codespaces 等云端 IDE 中,
00:00:39在 Portainer 或 Qualify 等基础设施工具中,
00:00:41甚至在 VS Code 或 Cursor 等桌面 IDE 中。
00:00:44但它们都使用 Xterm.js 来实现,
00:00:47因为它已经存在很长时间了,
00:00:49基本上是事实上的默认方案。
00:00:51不过 Xterm 有个大问题。
00:00:52它渲染到 canvas 元素中。
00:00:54所以像选择文本
00:00:56或在页面中查找单词这样的操作,
00:00:58都必须从零开始重新实现,
00:01:00而且效果并不总是那么好。
00:01:02Wterm 采取了完全不同的方法,
00:01:04即渲染到 DOM,
00:01:05这意味着终端输出只是 HTML,
00:01:08浏览器基本上可以免费处理这些内容。
00:01:10但 Wterm 也能用这种 HTML 渲染方式
00:01:13做一些非常酷的事情,
00:01:14比如只重新渲染更新的那一行,
00:01:17而不是在每一帧中重新渲染整个终端。
00:01:20它还可以写在不同的框架中,
00:01:22比如 React 或 Vue。
00:01:23你可以更改主题。
00:01:24还有一个独立的 Wterm ghosty 包,
00:01:27它用 lib ghosty 替换了 Zig 调用,
00:01:29而且比另一个网页 ghosty 项目
00:01:31工作得更好,
00:01:33我们会在视频后面深入讨论这一点。
00:01:35但现在,让我们用一个简单的演示项目
00:01:37试用一下 Wterm。
00:01:38安装 React 版 Wterm 后,
00:01:40我已经导入了组件和 CSS,
00:01:43并在那边渲染了该组件。
00:01:45现在如果我运行该应用并进入浏览器,
00:01:48可以看到有一个终端。
00:01:49但如果我输入像 ls 这样的命令,
00:01:51什么也不会发生。
00:01:52这是因为它没有连接到
00:01:53另一台电脑来读取信息。
00:01:56让我解释一下。
00:01:57因为现在客户端并没有连接到后端,
00:01:59所以它没地方获取信息。
00:02:01我们需要做的是把它连接到另一台机器。
00:02:04可以是我的本地机器,
00:02:06或者是云端机器,
00:02:07然后在该机器内渲染一个假终端或伪终端,
00:02:10其尺寸与客户端的终端相同。
00:02:13所以如果我们输入按键,
00:02:14按键信息会被发送
00:02:16到另一台机器,
00:02:18该机器执行这些按键,
00:02:20渲染结果,
00:02:22并将所有信息
00:02:23并将所有这些信息传回客户端。
00:02:25这种往返交互必须
00:02:26非常快速且低延迟。
00:02:28所以连接客户端和
00:02:30另一台机器的最好方式
00:02:31就是使用 WebSockets。
00:02:32让我们这样做吧。
00:02:34我们可以使用我已经设置好的 Ubuntu 服务器
00:02:35并安装了 Node。
00:02:37我还准备了一个
00:02:38带有服务器脚本的 Wterm 服务器。
00:02:40如果我们看一下它,
00:02:42可以看到我们正在创建一个
00:02:43WebSocket 服务器,挂载在
00:02:45/api/terminal 路径下。
00:02:48这一点稍后会更清楚。
00:02:49往下看,
00:02:49我们正在生成一个伪终端,
00:02:51其名称与我们的终端类型匹配。
00:02:53如果你好奇,这是查看你自己的方式。
00:02:55这里,
00:02:56我们接收来自客户端的任何按键,
00:02:58在服务器上处理它,
00:02:59也就是在我们的假终端里,
00:03:01然后把信息返回给
00:03:02那边的客户端。
00:03:03所以服务器返回所有内容,
00:03:05而不只是更新的那一行。
00:03:07现在在客户端的 app.tsx 文件中,
00:03:10我们正在通过 WebSocket 连接
00:03:11到我们服务器的 /api/terminal 端口。
00:03:14然后我们使用 Wterm 的 WebSocket 转换
00:03:16来连接该 URL
00:03:19并实现自动重连。
00:03:21这会发送按键信息
00:03:23从这里发到服务器。
00:03:24我们在这里处理浏览器调整大小,
00:03:26然后往下,
00:03:27handleData 函数
00:03:28处理来自服务器的
00:03:30所有信息。
00:03:31ZIG 核心的酷之处在于
00:03:33它会解析这些信息,
00:03:35弄清楚发生了什么变化,
00:03:36并只重新渲染那部分的 HTML。
00:03:39在这里,列和行的大小
00:03:41需要与我们在服务器上的一致,
00:03:42其他一切都相当直观。
00:03:45所以现在客户端和服务器都运行起来了,
00:03:47回到浏览器,如果我输入 LS,
00:03:49我们可以看到它列出了我们可以使用的文件。
00:03:52所以我可以用 -l 标志执行 LS,
00:03:53来查看关于文件的更多信息。
00:03:55我可以 CD 进入一个文件,
00:03:57看看里面的信息,
00:03:59也可以做一些事情,比如查看
00:04:01我正在运行的容器列表。
00:04:02甚至可以用 Vim 打开文件
00:04:03并在其中导航。
00:04:04但尽管所有这些都能工作,
00:04:06效果并不是特别好。
00:04:07我是说,如果我们要高亮显示一些文本,
00:04:09可以看到一些字符
00:04:10完全不可读。
00:04:12为了修复它,
00:04:13我们可以通过加载 Ghosty 核心
00:04:15并将其作为 React 中的 prop 添加,
00:04:16来使用 Ghosty 配置 Wterm。
00:04:18现在可以看到,如果我们打开服务器文件
00:04:20并高亮显示一些文本,
00:04:22一切都清晰多了。
00:04:23它甚至能做些事情,
00:04:24比如正确渲染 OpenCode,
00:04:26允许我们切换模型,
00:04:27并提供带有 Emoji 支持的提示词。
00:04:29我们甚至可以看到 Ghosty 渲染颜色的效果
00:04:31比使用默认的
00:04:31Wterm 核心渲染器稍微好一点。
00:04:34但 Zig 核心只有 12KB,
00:04:36而 Ghosty 则有 400KB。
00:04:39所以如果你关心大小,
00:04:40那也许坚持使用 Zig 核心。
00:04:43总之,这是 Vercel 提供的 Wterm 的快速概览。
00:04:46当然,还有更多功能我没有介绍,
00:04:48比如能够将 Markdown
00:04:49转换为漂亮的终端输出,
00:04:51如果你无法访问后端,
00:04:52只需使用 Bash 在假文件之间导航。
00:04:55甚至还有关于如何通过
00:04:57浏览器中的终端
00:04:58设置 SSH 客户端的示例。
00:05:00但我并没有发现 Wterm 是完美的。
00:05:02使用 Ghosty 版本时存在一些渲染问题,
00:05:05特别是在 NeoVim
00:05:06甚至 OpenCode 之间来回切换时。
00:05:08为了让 Ghosty 渲染器在我的
00:05:10BUN 前端中工作,
00:05:11我必须显式导入 WASM 文件,
00:05:13因为 BUN 不会从
00:05:15Node modules 文件夹中复制任何非 JS 文件。
00:05:17但我确实喜欢 DOM 渲染方法,
00:05:19这意味着你可以获得辅助功能
00:05:21和原生的浏览器特性,
00:05:23而无需做任何额外工作,
00:05:25尽管 Xterm 努力了这么久,
00:05:27也依然难以做到。
00:05:29不过 Xterm.js 确实拥有庞大的生态系统,
00:05:31而且是经过实战检验的解决方案,
00:05:33所以选择它也不会出错。
00:05:35还有由 Coda 开发的 GhostyWeb,
00:05:38它采取了不同的方法。
00:05:40它使用了相同的 libGhosty 引擎,
00:05:42也就是实际的 Ghosty 终端使用的那个,
00:05:43但它是 Xterm 的直接替代品,
00:05:45所以它仍然使用 canvas 渲染方法
00:05:48并且使用相同的 API,
00:05:50但你确实能得到一个更好的终端。
00:05:52(抱歉,之前的索引有误,
00:05:54这最后一个索引是正确结尾。)

Key Takeaway

Wterm 通过 DOM 渲染而非传统的 canvas 渲染,构建了仅需 12KB 的基础 WASM 二进制文件,在浏览器中实现了原生的终端交互体验与更佳的辅助功能兼容性。

Highlights

  • Wterm 采用 DOM 渲染方式,原生支持文本选择、浏览器搜索及屏幕阅读器,无需针对 canvas 重新实现这些功能。

  • 核心 Zig 代码编译后仅有 12KB,相比于常规终端模拟器大幅减少了资源占用。

  • 通过 WebSockets 连接远程服务器后,客户端仅需处理输入转发与部分 HTML 更新,实现了低延迟的远程终端交互。

  • 引入 400KB 的 Ghosty 核心可显著提升渲染清晰度,并支持更复杂的终端功能如 OpenCode 切换与 Emoji。

  • 相较于事实标准 Xterm.js,Wterm 专注于通过 DOM 渲染简化交互体验,但目前在复杂终端应用(如 NeoVim)的兼容性上仍存在优化空间。

Timeline

DOM 渲染与终端技术原理

  • Wterm 通过直接渲染到 DOM 替代 canvas,使浏览器能原生支持文本选择与搜索。
  • 核心引擎由 Zig 编写,仅产生 12KB 的 WASM 文件。
  • 支持可选的 LibGhosty 后端以增强终端兼容性。

传统的网页终端多基于 Xterm.js,受限于 canvas 渲染机制,导致文本选择与搜索等基础浏览器功能无法直接使用,需从零实现。Wterm 改变了这一路径,将终端输出作为 HTML 处理,并优化了渲染逻辑,仅重新绘制更新的部分行,而非每一帧重绘全屏。

WebSocket 实现远程连接

  • 客户端必须通过 WebSockets 连接至伪终端服务器以获取实时交互信息。
  • 服务器端实时处理按键输入,并返回更新信息至客户端。
  • Zig 核心自动解析差异信息,仅更新页面中变动的 HTML 部分。

单纯的前端组件无法读取系统信息,必须连接至本地或云端机器执行命令。系统通过 WebSocket 建立低延迟通道,将客户端的按键输入发送至服务器端运行的伪终端,服务器处理后将结果回传。此机制确保了即使在浏览器中也能运行 Vim 等复杂命令行工具。

性能优化与兼容性权衡

  • 加载 Ghosty 核心可解决渲染清晰度问题并提供更丰富的功能支持。
  • Ghosty 核心体积达 400KB,用户需在包大小与渲染效果之间进行权衡。
  • Wterm 目前在 NeoVim 等复杂应用环境下仍存在渲染瑕疵。

尽管基础版 Wterm 轻量,但特定文本渲染效果欠佳。集成 Ghosty 后端能解决此问题,并支持 Emoji 及更佳的颜色呈现。此外,该项目与主流构建工具的集成(如 Bun)在处理 WASM 文件时需要明确导入配置,且相较于生态成熟但采用 canvas 的 Xterm.js,Wterm 仍处于发展阶段。

Community Posts

No posts yet. Be the first to write about this video!

Write about this video