Sätteri 与 unified:Markdown 处理的两条路线
前几天看到一个消息,Astro 7 正式发布,默认换上了 Rust 写的 Sätteri 作为 Markdown 处理器。构建速度更快,插件更轻量,听起来一切都很美好。我的站点正好用 Astro,就顺手升级了。
结果 build 直接报错。原来 v7 只会默认启用 Sätteri,@astrojs/markdown-remark 也不再跟着 Astro 一起安装,而我在 astro.config.ts 里配的 remark-math 和 rehype-katex 都依赖 unified,处理器换掉后自然接不上了。
一开始还以为是配置格式又改了,围着插件折腾了半天。后来翻到 Astro v7 迁移指南,才发现升级和更换 Markdown 生态其实是两件事。想留在 Sätteri,可以把现有插件移植成它的 MDAST 或 HAST 插件;暂时不想移植,也能在 v7 里显式选回 unified,没必要连 Astro 一起降级。
unified 的流水线
先说说 unified 是怎么工作的。Markdown 文本先进 remark-parse,出来的是一棵 MDAST 语法树。然后 remark 插件在这棵树上做变换,接着 remark-rehype 把树转成 HAST,rehype 插件继续改,再 stringify 成 HTML。
这条流水线能跑通,全靠规范。MDAST 规定了 Markdown 节点长什么样,HAST 规定了 HTML 节点长什么样,unist 又给了共同的底层接口。三层规范叠在一起,插件之间才能互相协作。
remark-math 在 MDAST 阶段把 $...$ 识别成 math 节点,rehype-katex 在 HAST 阶段把这些节点转成 KaTeX 渲染后的 HTML。两个插件并不直接通信,但通过共享的 AST 规范间接配合。这种生态经过十年积累,已经有了几百个插件,常见需求基本都能找到现成方案。
但层数多了,AST 要被反复遍历。规范卡得太死,想做的事情如果不符合节点定义,就得绕路或者自定义类型。代价确实存在,只是以前没人太在意,因为生态太舒服了。
Sätteri 的插件模型
Sätteri 仍然会经过 MDAST 和 HAST 两个阶段,节点形状也尽量贴近已有规范,真正换掉的是解析器、编译器和插件接口。它把主要工作放到 Rust 里完成,插件则通过 defineMdastPlugin、defineHastPlugin 这类接口直接声明要访问和修改的节点。
少了 unified 运行时和 JavaScript 里的多轮遍历,构建时间确实能缩不少。自己写插件时也比较直接,可以按节点类型注册处理逻辑,不用再手动组织一套 visitor。
我在 Astro 7 里第一次体验这种架构时,构建速度的提升能感觉到。但把 remark-math 和 rehype-katex 原样塞回去仍然不行,虽然两边都在处理 MDAST 和 HAST,插件协议却不一样。remark 插件通常返回 transformer,Sätteri 插件则使用自己的定义和访问接口,现成包没法直接混用。
想继续用数学公式,可以把相关逻辑改写成 Sätteri 插件,节点结构相近,移植并非从零开始。麻烦在于生态还新,remark-math、rehype-katex 这类已经跑了很多年的组合暂时不能直接复用,迁过去还要自己测试插件之间的数据能不能顺利接上。
对新项目而言,如果只用基础 Markdown,或者插件不多,直接跟着默认配置走很省心。已有项目背着一串 remark、rehype、recma 插件时,处理器切换就得单独安排,没必要把它和框架升级绑在同一天折腾。
留在 v7
迁移指南给出的兼容路线很简单,先把 unified 处理器装回来:
pnpm add @astrojs/markdown-remark
再在 astro.config.ts 里显式选择 unified(),插件也放进处理器配置:
import { defineConfig } from "astro/config";
import { unified } from "@astrojs/markdown-remark";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
export default defineConfig({
markdown: {
processor: unified({
remarkPlugins: [remarkMath],
rehypePlugins: [
[rehypeKatex, { throwOnError: false, strict: false }],
],
}),
},
});
这样跑的仍然是 Astro 7,只把 Markdown 处理器换回了 unified,.md 和 .mdx 都会使用这条流水线。原来的 markdown.remarkPlugins、markdown.rehypePlugins 配置目前还能兼容,但已经被标记为废弃,把插件收进 unified() 里会更清楚。
改完再跑一次 build,KaTeX 正常渲染,其他 rehype 插件也都回来了。框架版本继续往前走,Markdown 这块先留在熟悉的生态里,等 Sätteri 的插件补齐后再决定什么时候迁过去。