本文是对 Introducing arborium, a tree-sitter distribution 的整理与翻译。内容结构概览问题起点docs.rs 上非 Rust 代码块通常没有彩色语法高亮体验很差。为什么不容易修历史文档不可变不能重建所有历史版本还要考虑语言数量、工具选择、体积、安全、平台兼容。rustdoc / docs.rs 背景Rust crate 文档由 rustdoc 生成发布到 crates.io 后由 docs.rs 构建和托管。tree-sitter 的价值它是高质量、快速、鲁棒、依赖少的解析器生成和增量解析工具适合语法高亮。LSP 为什么不适合离线高亮语义高亮更准但需要加载源码、依赖和 sysroot成本太高。tree-sitter 原始生态的麻烦要找 grammar、修旧 grammar、重新生成 C parser、找 highlight query 和 injection query。作者的动机过去 6 年为自己网站维护了一批 grammar后来决定把这件事做成公共基础设施。arborium 是什么一个 tree-sitter distribution整理了 96 种语言的 grammar、queries、license 和 feature flags。依赖联动例如启用 Svelte 时会自动带上 HTML、CSS、JavaScript、SCSS 等相关高亮依赖。高层 APIHighlighter::new()highlight_to_html(rust, ...)让使用方不用自己组装 tree-sitter。HTML 输出策略默认使用短标签如a-kkeyword/a-k也支持传统span classcode-keyword...。终端输出支持除了 HTML也能输出带 ANSI escape 的终端高亮文本。WASM 支持难点目标是wasm32-unknown-unknown需要提供一组 libc 符号和 allocator。方案一在文档里引入前端脚本今天就能用docs.rs 无需改动但 bundle 大且安全风险严重。方案二集成进 rustdoc技术上可行作者做了 PR但 96 种语言会让 rustdoc 二进制从约 22MB 增到约 171MB。方案三docs.rs 后端后处理用arborium-rustdoc扫描 rustdoc HTML、给 code block 上色、补 CSS。后处理方案的优势不需要第三方 JS不增加用户下载 grammar bundle体积增量很小还可以 sandbox。构建工程挑战96 种语言、双平台发布、provenance、CI、WASM import 检查、xtask、缓存等都很折腾。最终愿景把 tree-sitter 高质量语法高亮能力打包成可长期维护的公共基础设施。Rust 文档体验一直很强。你在代码里写///或//!rustdoc 就能生成漂亮的 HTML 文档。发布 crate 到 crates.io 后docs.rs 会自动构建文档给每个 crate 一个可以访问、可以链接、可以搜索的页面。对于 Rust 生态来说这是一套非常重要的基础设施。但这套基础设施里有一个长期看着不太舒服的小问题代码块高亮。Rust 代码块当然能高亮。但如果你的文档里有 Bash、TOML、YAML、JSON、C、C、JavaScript、TypeScript、HTML、CSS、SQL、Dockerfile、Nix、Svelte、Python、Go 等其他语言很多时候它们只是白字黑底。你明明写了语言标记浏览器里看起来却像没上色一样。这件事看起来简单不就是加个语法高亮吗但原文的重点就是这件事一点都不简单。因为 docs.rs 不是个人博客。它托管了大量 crate 的文档而且历史版本几乎是不可变的。一旦某个 crate 的某个版本构建完成它生成的 HTML、CSS、JS 会被放进巨大的存储桶里。你不能说“我们现在想给代码块上色了那就把所有 crate 的所有历史版本全部重新 rustdoc 一遍”。这在工程上不可行。同时语法高亮本身也有很多选择题用哪套高亮引擎 支持哪些语言 能不能信任它 输出质量够不够 需不需要动态链接 rustdoc 支持的平台上都能编译吗 高亮后的 HTML 会变大多少 体积增加能不能接受 谁来维护这些语言 grammar 谁来负责许可证和归属声明这篇文章介绍的 arborium就是作者对这些问题的回答。简单说arborium 是一个 tree-sitter distribution。它把许多 tree-sitter grammar、highlight query、injection query、许可证信息、构建规则、WASM 兼容性和 Rust API 包装起来让开发者不用自己一点点拼装语法高亮基础设施。更野一点说它试图把“高质量、多语言、可复用、可部署”的语法高亮变成 Rust 文档生态可以长期依赖的一块公共基础设施。一、问题起点为什么 docs.rs 上很多代码块没有颜色故事从一次和 docs.rs 团队的讨论开始。作者看到自己发布的 crate 文档里有很多不同语言的代码块但它们看起来基本都是白字黑底。这让人很难受。文档里的代码块如果没有高亮阅读体验会差很多。尤其是当一个 crate 文档需要展示配置文件、shell 命令、HTML、CSS、JavaScript、TOML、YAML 或其他语言时黑白文本非常不友好。于是作者开了一个 GitHub issue想了解为什么 docs.rs / rustdoc 不能直接支持更多语言的语法高亮。讨论很短但很有成果。结果是不是大家不知道它有用而是背后有现实原因。首先docs.rs 的历史文档不是可以随便重写的。一个 crate 版本构建完成后它生成的 HTML、CSS、JS 会被放进存储桶。后续如果 rustdoc 改了高亮逻辑也不会自动重建所有历史版本。docs.rs 可能会重新构建最新版本但不会把所有历史版本全部扫一遍。所以想要给“已经存在的所有历史文档”加颜色不能只靠改 rustdoc。你没法承受全量重建成本。其次语法高亮不是一个开关。你要先决定用什么高亮器。正则syntecttree-sitterLSP每个选择都有代价。你还要决定支持多少语言。支持 Rust 之外的 5 种语言20 种100 种谁来定按下载量按投票按作者心情你还要考虑输出体积。高亮后的 HTML 一定比原始代码块大。每个 token 都包一层 span 或自定义标签所有文档加起来存储和传输体积都会增加。你还要考虑安全。语法高亮如果在浏览器里运行就意味着加载 JavaScript甚至 WebAssembly。docs.rs 是公共基础设施让任意 crate 作者向文档页面注入第三方 JS本质上是安全风险。所以“为什么不直接上色”这个问题答案不是“没人想做”而是“做起来要同时解决一堆工程、生态、体积、安全和维护问题”。二、tree-sitter 为什么适合做这件事作者选择的核心工具是 tree-sitter。tree-sitter 是一个解析器生成工具也是一套增量解析库。它可以为源文件构建具体语法树并在源文件修改时高效更新语法树。它的目标是通用、快速、鲁棒、依赖少。对代码编辑器来说tree-sitter 已经非常重要。很多编辑器用它做语法解析、高亮、代码折叠、结构化选择等功能。它不像简单正则高亮那样脆弱也不像完整语言服务器那样重。作者认为如果只看语法高亮质量tree-sitter 是非常好的选择。只有 LSP 语义高亮能比它更强。但 LSP 不适合这里。LSP也就是 Language Server Protocol是 rust-analyzer 和编辑器之间说话的协议。LSP 能做语义高亮因为它理解项目、依赖、类型、模块、符号等上下文。但要做到这些它需要加载源码、依赖、sysroot甚至整个项目图。对一个编辑器来说可以接受对离线生成一堆文档页面来说成本太高。rustdoc / docs.rs 只是要给代码块上色。为了一个小代码块启动语言服务器、加载依赖、分析整个 crate显然不现实。tree-sitter 的定位正好在中间比正则强比 LSP 轻。它能用语法树判断什么是关键字、函数、字符串、数字、注释也能通过 query 机制表达高亮规则。对于文档语法高亮来说这是一个很合适的平衡点。三、tree-sitter 不是拿来就能用虽然 tree-sitter 很好但原始生态并不是“引一个 crate 就完事”。如果你想高亮某种语言需要几个东西。第一需要 grammar。也就是该语言的 tree-sitter 语法定义。Rust、C 这类主流语言通常有高质量 grammar但冷门语言就不一定。有些 grammar 很旧是针对旧版本 tree-sitter 写的需要重新生成。有些 grammar 里还有会让编译时间爆炸的规则需要清理。第二需要运行 tree-sitter CLI 重新生成 parser。tree-sitter grammar 通常有grammar.js有时还有scanner.cc或scanner.c。重新生成后会得到一大堆 C 代码。最终每种语言的 parser 往往就是一堆生成出来的 C 文件。第三需要 highlight query。grammar 只会告诉你代码的语法树长什么样。它能告诉你这里是某个 node那里是某个 expression但它不会自动告诉你“这个 node 应该是 keyword”、“这个 node 是 function”、“这个 node 是 string”。highlight query 就是把语法树 node 映射到高亮类别的规则。第四需要 injection query。很多语言里会嵌套其他语言。比如 Svelte 组件通常包含 HTML又嵌入script和style里面可能是 JavaScript、TypeScript、CSS、SCSS。Markdown 里有 fenced code block也可能嵌入任意语言。injection query 用来告诉高亮器这里有另一个语言要切换 grammar 继续高亮。第五需要把这些依赖组织起来。tree-sitter-highlight 确实提供了 callback 系统处理 injection但具体依赖怎么带、怎么调度、怎么让 Svelte 自动带上 HTML/CSS/JS都要你自己实现。所以作者之前为了自己网站维护了一个私有 grammar 集合。最开始只有 18 种语言Bash、C、Clojure、Dockerfile、Go、HTML、INI、Java、JavaScript、Meson、Nix、Python、Rust、TOML、TypeScript、x86asm、YAML、Zig 等。后来他意识到自己不止一个项目需要高亮别人也会遇到同样问题。既然已经踩坑 6 年不如把这些东西整理成公共分发。于是 arborium 出现了。四、arborium 是什么arborium 是一个 tree-sitter distribution。它不是单个 parser也不是一个简单的 wrapper。它更像一个经过整理、修复、打包、带许可证和统一 API 的 tree-sitter 语言发行版。作者根据投票需求整理了 96 种语言。对每一种语言他都做了这些事寻找可用且质量较好的 grammar 把 grammar vendored 进项目 修复或清理旧 grammar 重新生成 parser 确认 highlight query 能工作 确认 injection query 能工作 确认许可证和 attribution 信息齐全 把语言接入 arborium 的 feature flagarborium 的目标不是让你自己到处找 grammar、修 grammar、修 query、处理 injection、配置 theme、处理 HTML 输出而是给你一个现成入口。最简单的使用方式大概是usearborium::Highlighter;letmuthighlighterHighlighter::new();lethtmlhighlighter.highlight_to_html(rust,fn main() {})?;当然这个高层 API 牺牲了一部分 tree-sitter 的增量解析能力。tree-sitter 的强项之一是编辑器场景中的增量更新文件改一点语法树只更新一部分。但文档高亮通常不是这个场景。文档构建时一段代码块就是一次性输入一次性输出 HTML。所以简单 API 很有意义。如果你需要更复杂的 APIarborium 也提供更底层的接口。但对很多使用者来说highlight_to_html就够了。五、语言 feature 和依赖联动arborium 把每种语言做成 cargo feature。这样使用者可以按需启用语言避免把所有 96 种语言都编进来。但它不只是“一个语言一个 feature”这么简单。有些语言需要依赖其他语言才能完整高亮。比如 Svelte。Svelte 组件本身不是单一语言它通常包含 HTML、JavaScript、CSS有时还有 TypeScript、SCSS 等。如果你启用 Svelte高亮器必须知道如何处理嵌入的脚本和样式。所以 arborium 会做依赖联动。启用svelte时它会自动带上 HTML、CSS、JavaScript、SCSS 等相关 grammar crate。这样用户不用手动猜“我高亮 Svelte 还要启用哪些语言”。这很重要。因为 tree-sitter injection 的麻烦点就在这里你不能只拿到外层 grammar还要知道嵌套语言怎么解析、怎么高亮、怎么加载。arborium 把这些复杂性收进 distribution 里。六、HTML 输出短标签还是传统 span语法高亮最终要输出 HTML。最传统的方式是spanclasscode-keywordkeyword/span这很直观也和很多高亮器一致。缺点是体积较大。每个 token 都有很长的标签和 class 名大量代码块叠起来HTML 体积会明显增加。arborium 默认选择更短的现代标签例如a-kkeyword/a-k这类标签短得多。只要配套 CSS 写好浏览器可以正常渲染。它不是最传统的写法但更紧凑。如果你坚持传统风格arborium 也支持长写法spanclasscode-keywordkeyword/span作者开玩笑说如果你复古并且保证 Brotli 压缩能弥补体积那也可以用这种方式。这个设计背后其实是 docs.rs 场景的现实高亮 HTML 体积不能无限膨胀。Rust 文档里代码块非常多如果每个 token 都膨胀很多长期存储和传输成本都会上升。所以 arborium 在 API 层就提供输出风格选择你可以选紧凑也可以选传统。七、不只是 HTML也能输出终端高亮arborium 不只面向网页。如果你是终端用户也可以让它输出带 ANSI escape code 的高亮文本。甚至可以配置背景色、margin、padding、border让代码块在终端里也有漂亮的样式。这说明 arborium 并不只是 docs.rs 的专用工具。它更像一个通用语法高亮基础设施HTML 场景能用终端场景也能用文档生成能用网站构建能用CLI 工具也能用。核心价值仍然是你不需要自己去找 96 种 grammar也不需要手动拼 tree-sitter-highlight 的 callback。八、WASM 支持wasm32-unknown-unknown的麻烦arborium 还有一个重要目标能够编译到wasm32-unknown-unknown在浏览器中运行。这很关键因为作者的第一个落地方案就是让文档页面加载一个前端脚本和 WebAssembly bundle在浏览器里完成高亮。但这件事并不简单。tree-sitter grammar 最终会生成 C 代码。Rust crate 调用 C 编译器把这些 parser 编译进来。要编译到 WebAssembly就需要目标平台能提供 C 代码需要的一些 libc 符号。tree-sitter 自带的 WASM playground 通常使用wasm32-wasi。WASI 提供一套系统接口类似“WebAssembly 的系统调用层”。但作者需要的是wasm32-unknown-unknown这是一个更裸的目标。它没有完整系统环境。你需要自己提供足够多的 libc 符号让生成的 C parser 能满意。arborium 为此准备了一个小型 sysroot提供assert.h、ctype.h、endian.h、inttypes.h等头文件以及一些函数实现。大多数函数很简单比如isupper、islower。比较麻烦的是malloc、free这类内存分配函数arborium 使用dlmalloc来提供。最终效果是这些 Rust crate 可以通过 Rust toolchain 和 C toolchain 编译到wasm32-unknown-unknown然后在浏览器里运行。这一步很工程化也很关键。没有它前端脚本方案就无法成立。九、方案一直接在文档里引入脚本第一个落地方案最直接如果你发布 crate想让 docs.rs 上的文档支持多语言高亮可以在仓库里放一个 HTML 文件然后在Cargo.toml里加 metadata让 docs.rs 构建时把它带进去。这个 HTML 文件加载 arborium 的脚本和 WASM在页面加载后扫描代码块给它们上色。作者还额外做了一些适配检测页面是否运行在 docs.rs 上并根据当前 docs.rs 主题切换对应高亮主题。docs.rs 有 light、dark、Ayu 等主题arborium 会尽量保持风格一致。这个方案的优点非常明显今天就能用 不需要 docs.rs 团队改任何东西 不需要改 rustdoc 不需要改构建管线 crate 作者可以自己选择接入这类前端扩展在 rustdoc 里已经有人做过比如集成 KaTeX 渲染 LaTeX 公式、渲染图表等。rustdoc 页面允许 crate 文档带一些前端资源因此这是一条现实可行的 escape hatch。但作者马上说这个方案也是最糟糕的。原因有两个。第一体积。它需要 JavaScript 和 WebAssembly。为了给几个小代码块高亮用户可能要下载几百 KB 的 grammar bundle。对网页性能来说不理想。第二也是最重要的安全。让任意 crate 作者在 docs.rs 页面主上下文里注入第三方 JavaScript是一个安全灾难。今天 docs.rs 页面上没什么可偷顶多是用户选择的主题。但未来不一定。更重要的是这是一种坏实践。想象一下如果所有人都用 arborium 的 NPM 包来给 docs.rs 高亮。几年后作者突然变坏发布一个恶意版本。只要页面加载这个包就可能影响大量用户。即使让大家 pin 固定版本也会阻止他们获得安全更新和 bug 修复。理想状态是docs.rs 页面上的 JavaScript 都应该由 docs.rs 团队自己分发。这样世界只需要担心 docs.rs 团队变坏而不是每个 crate 作者或第三方包作者变坏。所以前端脚本方案能用但不应该是长期答案。十、方案二把 arborium 集成进 rustdoc第二个方案是把 arborium 直接集成到 rustdoc。arborium 本质上是一堆 Rust crate加上一堆 tree-sitter 生成的 C 代码。它没有动态链接没有 plugin folder没有异步加载也没有运行时乱七八糟的外部依赖。理论上把它放进 rustdoc 是可行的。作者甚至做了一个 rustdoc PR让 rustdoc 能高亮其他语言。代码 diff 看起来不大大概是537 -11。但这只是表面。真正被拉进来的是数百万行 tree-sitter 生成的 C parser 代码。这马上带来一个问题到底要打包哪些 grammar如果把 96 种语言都编进 rustdoc二进制体积会明显膨胀。作者展示了一个对比带 96 种语言的自定义 rustdoc 大约 171MB而 main branch 的 rustdoc 大约 22MB。这不是小差距。这也是为什么“集成进 rustdoc”虽然技术上可行但政治和工程上都不一定好过。rustdoc 是 Rust 工具链的一部分每个安装 Rust 的人都会拿到它。为了文档里偶尔出现的多语言代码块把 rustdoc 变大这么多肯定会引发讨论。也许可以只内置少数常见语言那谁来决定也许可以 feature-gate但 rustdoc 是随 toolchain 分发不是每个 crate 自己 cargo feature。也许可以做外部插件那又回到分发、安全、平台兼容问题。所以方案二很干净但代价很重。十一、方案三只在 docs.rs 后端处理于是作者提出第三个方案不要让浏览器处理也不要让 rustdoc 自带所有 grammar而是在 docs.rs 后端构建完成后做一次 HTML 后处理。这个工具叫arborium-rustdoc。它专门处理 rustdoc 生成的 HTML扫描 HTML 文件 找到代码块 识别语言 用 arborium 高亮 把原代码块替换成带高亮标记的 HTML 给主 CSS 文件底部补上样式它不是浏览器脚本也不是 rustdoc 内置功能而是一个构建后处理器。作者测试了 facet monorepo 的所有依赖文档。约 900MB 的文档目录处理后只增加了 24KB。这个结果非常重要因为它直接回应了“高亮后 HTML 会不会变大很多”的担忧。后处理方案有几个优点不需要第三方 JS 不需要用户下载 WASM grammar bundle 不需要把 96 种语言塞进 rustdoc 二进制 可以在 docs.rs 后端统一执行 可以 sandbox 可以支持所有语言 对最终页面体积影响很小作者最后也明确说如果是 docs.rs他现实中会选择arborium-rustdoc作为后处理步骤。它快可以支持所有语言也没有前两个方案的安全和 bundle size 问题。这也是整篇文章里最像“工程上可落地”的答案。十二、CI 和构建工程真正痛苦的地方文章最后的 post-mortem 讲了项目中最难的部分CI。如果只是构建一个小 Rust 包GitHub Actions 还算能忍。但 arborium 不是一个小包。它要处理 96 种语言支持多个包构建 WebAssembly发布到两个平台还要带 provenance。作者提到这是2x96 builds supporting packages的规模。任何一个 CI 失败都很惩罚。为了让构建可维护他把大量逻辑从 YAML 里拿出来放进cargo-xtask。xtask是 Rust 项目里常见的一种模式在仓库里放一个小工具 crate用 Rust 写构建、生成、检查、发布等工程脚本。相比在 GitHub Actions YAML 里塞复杂 bashRust 代码更容易测试、组织和维护。arborium 的 xtask 不只是显示进度条和 nerd font 图标。它还会检查每个生成 artifact 是否真的能在浏览器里加载。具体做法是用walrus解析 WebAssembly bundle检查它的 imports而不是简单地把wasm-objdump -x输出 grep 一下。它还用 blake3 hash 避免重复计算输入。作者开玩笑说一方面是因为名字听起来酷另一方面是因为两周里发生了太多疯狂事情他已经记不清一半了。这段说明arborium 不只是“收集 grammar”这么简单。真正难的是把它做成一个可发布、可检查、可重复构建、可跨平台运行的 distribution。十三、arborium 的意义把 6 年私人经验公共化这篇文章最有价值的地方是它展示了“基础设施化”的过程。一开始作者只是为自己网站解决语法高亮。他用了 tree-sitter找 grammar修 grammar写 query处理 injections。后来他维护了 18 种语言。再后来他发现自己多个项目都需要这些东西。最后他决定把这套私有工具变成公共组件。这就是 arborium不是一个简单库 不是一个 NPM 小脚本 不是一个 tree-sitter wrapper 而是一个把 grammar、queries、license、feature、WASM、HTML 输出、终端输出、rustdoc 后处理都打包起来的 distribution它的目标不是展示一个技术 demo而是让别人不必再重复踩同样的坑。作者最后说他希望 arborium 能撑未来 20 年。他以 Apache-2.0 MIT 许可把它捐给公共领域希望准确的语法高亮能在 Web 上开花就像代码编辑器这些年突然变得更擅长语法高亮一样。他相信 tree-sitter 可以第二次改变世界。第一次是在编辑器里第二次则是为那些没有时间、没有经验、没有兴趣自己拼装所有零件的人提供一个现成的分发包。十四、为什么这件事值得 Rust 生态关注Rust 生态非常重视文档。很多 crate 的 docs.rs 页面就是事实上的官方文档。一个 crate 好不好用文档读起来清不清楚非常关键。而现代文档里代码块不再只有 Rust。你可能要展示Cargo.toml YAML 配置 JSON 示例 Shell 命令 Dockerfile GitHub Actions workflow HTML/CSS/JS SQL Nix Svelte Python Go C/C如果这些代码块都没有高亮阅读体验会明显下降。尤其是配置文件和嵌入语言语法高亮能帮助读者快速分辨 key、value、string、comment、keyword。但文档高亮不是“好看”这么简单。它还影响认知负担。好的高亮能让读者更快找到结构减少看错代码的概率。对长文档、教程、API reference 来说这很重要。arborium 的意义在于它把这件本来每个人都要自己拼的东西变成一个公共包。十五、三种方案的取舍可以把文章里的三种方案放在一起看。方案一前端脚本优点今天就能用 crate 作者自己接入 docs.rs 团队零改动 不需要等 rustdoc 或 docs.rs 改造缺点需要 JavaScript 需要 WebAssembly 用户可能下载大 grammar bundle 安全风险严重 不适合作为长期公共基础设施它适合作为逃生通道不适合作为最终方案。方案二集成进 rustdoc优点技术上干净 生成文档时直接高亮 不需要运行时脚本 不需要 docs.rs 后处理缺点rustdoc 二进制会显著变大 必须决定内置哪些语言 把大量 tree-sitter 生成 C 代码带进 Rust 工具链 讨论成本和维护成本都高它是理想主义方案但代价很大。方案三docs.rs 后端后处理优点不需要第三方 JS 不增加浏览器端 grammar bundle 不把所有 grammar 塞进 rustdoc 可以支持所有语言 可以 sandbox 处理后页面体积增量很小 适合 docs.rs 集中执行缺点需要 docs.rs 构建流程增加一步 需要维护后处理工具 需要保证 HTML patch 稳定作者现实中最看好第三种。这也很符合工程直觉如果 rustdoc 本体不适合变得巨大浏览器端又有安全和体积问题那么后端构建后处理就是一个很合理的折中。十六、这篇文章真正想讲什么表面上文章是在介绍 arborium。更深层它讲的是“把一个生态里的反复痛点做成可复用基础设施”。tree-sitter 本身已经很好但它不是完整产品。一个 grammar crate 通常只导出一个 language 函数可能还附带 highlights query 和 injections query。它离“拿来高亮任意代码块”还有很远。你需要选 grammar修 grammar重生成 C parser处理 query处理 injection处理主题处理 HTML 输出处理终端输出处理 WASM处理 licenses处理 feature flags处理 CI处理发布处理 docs.rs 集成路径。arborium 的价值就在于把这些杂活收进一个 distribution。这也是很多基础设施项目的本质它们不是发明某个全新理论而是把一堆别人都不想重复做的脏活、累活、边角问题、平台差异、许可证和构建细节整理干净提供一个稳定接口。这类工作不总是最炫但非常重要。十七、总结这篇文章介绍了 arborium一个面向 tree-sitter 的 Rust 分发项目。它的起点是 docs.rs / rustdoc 里的多语言代码块高亮问题。Rust 文档生成体验很好但非 Rust 语言的代码块经常没有彩色高亮。想修这个问题并不容易docs.rs 上历史文档几乎不可变不能重建所有 crate 的所有版本高亮器选择、语言支持范围、输出体积、平台兼容、安全边界和维护成本也都需要考虑。作者选择 tree-sitter 作为基础。tree-sitter 比正则高亮更准确又比 LSP 语义高亮更轻。LSP 需要加载源码、依赖和 sysroot不适合离线批量文档高亮tree-sitter 则能用 grammar 生成语法树再通过 highlight query 和 injection query 映射到高亮类别和嵌套语言。但 tree-sitter 原始生态并不是拿来就能用每种语言都要找 grammar、修旧 grammar、重新生成 C parser、找 highlight query、处理 injection callback、处理嵌套语言依赖。arborium 正是为了解决这些重复劳动。作者整理了 96 种语言的 grammar根据投票需求寻找可用实现vendored 进项目修复和重新生成 parser确保 highlight query 和 injection query 正常补全许可证和 attribution并把每种语言集成进 cargo feature。对于 Svelte 这类嵌套语言启用 Svelte 会自动带上 HTML、CSS、JavaScript、SCSS 等相关 grammar。使用者可以通过主 crate 的简单 API 调用高亮比如创建Highlighter后调用highlight_to_html(rust, fn main() {})。arborium 不只支持 HTML。它支持不同主题默认使用紧凑 HTML 标签如a-kkeyword/a-k也支持传统span classcode-keywordkeyword/span输出。它还能输出带 ANSI escape code 的终端高亮文本。更重要的是它能编译到wasm32-unknown-unknown让浏览器端运行成为可能。为此作者还提供了一套足够让 tree-sitter C parser 满意的 libc 符号和 allocator其中malloc、free等由dlmalloc提供。文章提出三种落地方式。第一种是今天就能用的前端脚本crate 作者在文档里加入 HTML 和 metadata让 docs.rs 带上脚本和 WASM在浏览器中扫描代码块并高亮。它的优点是无需 docs.rs 团队改动马上可用缺点是需要下载 JavaScript 和 WebAssembly bundle体积可能很大而且允许第三方脚本进入 docs.rs 页面主上下文是严重安全风险。因此它适合作为 escape hatch不适合作为长期方案。第二种是把 arborium 集成进 rustdoc。作者已经做了一个 PR技术上能让 rustdoc 直接高亮其他语言。但问题是体积集成 96 种语言后rustdoc 二进制从约 22MB 增到约 171MB。虽然代码 diff 看起来不大背后却拉入了数百万行 tree-sitter 生成的 C parser。于是“应该内置哪些 grammar”变成一个很难的政策和工程问题。第三种是 docs.rs 后端后处理。作者做了arborium-rustdoc专门扫描 rustdoc 生成的 HTML找到代码块进行高亮并在 CSS 文件底部追加样式。这个方案不需要第三方 JS不需要浏览器下载 grammar bundle也不需要把 96 种语言塞进 rustdoc。作者测试 facet monorepo 依赖文档约 900MB 的文档目录处理后只增加 24KB。最终他认为如果为 docs.rs 现实落地后处理方案最合理快、支持所有语言、安全边界清楚还可以 sandbox。文章最后还讲了构建工程。arborium 最难的部分不是写一个高亮函数而是 CI 和发布96 种语言、双平台、WebAssembly、provenance、支持包、GitHub Actions、artifact 检查、缓存。作者把大量逻辑移出 YAML写进cargo-xtask并用walrus解析 WebAssembly bundle 检查 imports用 blake3 hash 避免重复计算。这说明 arborium 是一个工程化 distribution而不只是一个 demo。最终arborium 的意义在于把作者过去 6 年为自己网站和项目积累的 tree-sitter 语法高亮经验变成公共基础设施。它希望让那些没有时间、经验或兴趣自己拼装 grammar、query、injection、WASM 和主题系统的人也能获得高质量语法高亮。对 Rust 文档生态来说这可能是一个很现实的补足让 docs.rs 上不只是 Rust 代码块好看也让配置、脚本、前端、系统语言和各种嵌入代码都变得可读。