Astro 的 Sätteri 处理器进化史:从 @astrojs/markdown-satteri 到 Astro 默认 Markdown 引擎
【免费下载链接】astroThe web framework for content-driven websites. ⭐️ Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/as/astro
Astro(内容驱动型网站框架)的 Markdown/MDX 渲染管线在本仓库中已经完成了一次重大换血:基于 Rust 编写、以 WASM/JS 形式接入的 Sätteri 引擎,通过@astrojs/markdown-satteri包逐步演进(0.2.0 → 0.4.0),最终在 Astro 7.x 中成为markdown.processor的默认实现。本文以 packages/markdown/satteri/CHANGELOG.md 为时间线骨架,结合该包的源码与测试,完整梳理其引入动机、配置方式、语法高亮、Frontmatter 修改、MDX 编译等关键能力,读完即可理解并上手这套新一代 Markdown 管线。
一、它是什么:把 Rust 管线 Sätteri 接入 Astro 的markdown.processor
Astro 的 Markdown 渲染从设计上就是可插拔的:markdown.processor配置项接收一个MarkdownProcessor对象。历史上该位置默认由@astrojs/markdown-remark(基于 unified/Remark 生态)占据,而@astrojs/markdown-satteri提供了一个新的选择。
根据 0.2.0 版本的发布说明:该包将 Sätteri——一个用 Rust 编写的 Markdown 管线——封装为 Astro 可用的处理器。发布说明明确表述了两点动机:
- 快:项目方描述它“比默认的基于 Remark 的处理器快得多”;
- 开箱即用:原生支持广泛的 Markdown 特性,无需再为 GFM、Smart Punctuation 等行为额外安装插件。
值得注意的是,该说明还写道“我们计划在未来让这成为 Astro 的默认 Markdown 处理器”。在本仓库当前状态下这一规划已经落地:Astro 7.2.10 的核心配置 schema 中,markdown.processor的默认工厂函数即为satteri(),见 packages/astro/src/core/config/schemas/base.ts;同时 packages/astro/package.json 已将@astrojs/markdown-satteri列为依赖。因此,这篇演进记录不只是一份第三方集成的说明,更对应着当前 Astro 主版本的实际默认行为。
从源码结构看,整个包分为三个层次(见 packages/markdown/satteri/src):
| 文件 | 职责 |
|---|---|
processor.ts | 定义satteri()工厂函数与选项类型,暴露给用户配置入口 |
satteri-processor.ts | 实现.md文件的createRenderer渲染器与内置 hast/mdast 插件 |
mdx/create-processor.ts | 实现.mdx文件的createMdxRenderer编译器与布局/字符集包装 |
二、版本里程碑速览(0.2.0 → 0.4.0)
该 CHANGELOG 采用 0.2.0 起逐版记录,时间线文件 中的关键变化可整理如下:
| 版本 | 类型 | 核心变化 |
|---|---|---|
| 0.2.0 | Minor | 首次发布:引入基于 Sätteri 的 Markdown 处理器(当时不支持 Prism 高亮) |
| 0.2.1 | Patch | 修复发布时缺失的 provenance 供应信息 |
| 0.2.2 | Patch | Sätteri 升级至 v0.8.0 |
| 0.3.0 | Minor | 新增 Prism 语法高亮支持(syntaxHighlight: 'prism') |
| 0.3.1 | Patch | Sätteri 升级至 v0.9.0;支持通过插件以编程方式修改 Frontmatter |
| 0.3.2 | Patch | 修复标题在页面headings元数据中被列出两次的问题 |
| 0.3.4 | Patch | 修复 MDX 下自定义pre组件未作用于高亮代码块的问题 |
| 0.3.7 | Patch | Sätteri 升级至 v0.10.3 |
| 0.3.8 | Patch | 处理器选项类型支持 Sätteri v0.10.3 全部插件条目;修正smartPunctuation编辑器提示的默认值文案 |
| 0.4.0 | Minor | unified()与satteri()均可直接编译.mdx文件 |
版本推进过程中还出现过 0.3.1-beta.x、0.3.0-alpha.0 等预发布号,表明该功能在早期曾以 alpha/beta 节奏验证;正式版则一路稳定到 0.4.0。每个版本都随附@astrojs/internal-helpers的依赖升级,说明它深度依赖 Astro 共享的内部工具层(如 markdown 默认值、Shiki 高亮封装等,见 package.json 依赖声明)。
三、安装与最小配置
安装与启用方式自 0.2.0 起保持一致。在项目中安装:
npm install @astrojs/markdown-satteri然后在astro.config.mjs中把处理器指向satteri():
// astro.config.mjs import { satteri } from '@astrojs/markdown-satteri'; export default defineConfig({ markdown: { processor: satteri(), }, });0.2.0 发布说明特别强调了一个当时的限制:该处理器最初不支持 Prism 语法高亮,需要保持syntaxHighlight: 'shiki'(即默认值)或干脆关闭语法高亮。这一限制在 0.3.0 中被解除(见下文第五节)。
一个容易被忽略的实现细节是:Sätteri 的 Rust/WASM 二进制采用懒加载策略。在 satteri-processor.ts 的loadSatteri()中,satteri模块只有在处理器真正运行时才通过动态import()加载,避免在未使用 Sätteri 的构建流程中白白引入 WASM 体积。
四、satteri()API:选项全解与类型约束
satteri()工厂函数定义在 processor.ts,其选项类型SatteriProcessorOptions包含三部分:
| 选项 | 类型 | 作用 |
|---|---|---|
mdastPlugins | MdastPluginList | 在 mdast(Markdown 语法树)阶段追加的插件 |
hastPlugins | HastPluginList | 在 hast(HTML 语法树)阶段追加的插件 |
features | SatteriFeatures | 开关 Markdown 语言特性(gfm、smartPunctuation) |
工厂函数返回的处理器对象带有name: 'satteri'标识、已归一化的options字段,以及两个渲染入口:
createRenderer(shared)—— 用于.md文件,转发给createSatteriMarkdownProcessor;createMdxRenderer(shared, mdx)—— 用于.mdx文件(0.4.0 起可用),转发给createSatteriMdxProcessor。
其中options字段在构造时就会把未传入的插件数组折叠为[]、把features折叠为空对象{},因此集成方可以放心地写processor.options.features.gfm = false而无需先判空。工厂代码注释明确了这一设计意图(见 processor.ts)。
由于options需要保持引用同一性(配置 schema 的校验注释指出用z.custom而不是z.record来避免深拷贝破坏createRenderer闭包对processor.options的读取,见 base.ts),实践中处理器对象常常被 Astro 配置校验流程直接复用。
五、语法高亮:从 Shiki-only 到 Prism 支持
默认高亮是 Shiki。internal-helpers 的默认值 显示:syntaxHighlightDefaults为{ type: 'shiki', excludeLangs: ['math'] },Shiki 默认主题为github-dark,并默认排除math语言(数学代码块由其他机制处理)。
0.3.0 起支持 Prism。版本说明给出了切换配置:
// astro.config.mjs import { satteri } from '@astrojs/markdown-satteri'; export default defineConfig({ markdown: { processor: satteri(), syntaxHighlight: 'prism', }, });这一能力由 createHighlightFn 统一驱动:无论 Shiki 还是 Prism,最终都归结为一个把代码块转换为<pre>hast 节点的HighlightFn。返回真实 hast 节点(而不是原始 HTML 字符串)的关键收益是:<pre>仍可被 MDX 管线寻址,从而保证用户自定义的components.pre覆盖在高亮块上依然生效——这正是 0.3.4 修复所保障的行为(当时修复了 MDX 下自定义pre组件未生效的缺陷)。Prism 分支内部复用@astrojs/prism/dist/highlighter,再通过 Sätteri 的htmlToHast把生成的 HTML 转回 hast。
高亮阶段由createHighlightPlugin完成:它只针对<pre>元素、向下寻找<code>子节点读取语言与 meta,并跳过excludeLangs与默认排除列表['math'](见 createHighlightPlugin)。
highlight.test.ts 对上述行为有完整验证:
- 默认 Shiki 高亮会产出内联
background-color:样式; math代码块默认不高亮;syntaxHighlight: { type: 'shiki', excludeLangs: ['mermaid'] }可逐语言排除;prism模式产出<pre class="language-js">// astro.config.mjs import { satteri } from '@astrojs/markdown-satteri'; export default defineConfig({ markdown: { processor: satteri({ features: { gfm: false, smartPunctuation: false }, }), }, });smartPunctuation还支持细粒度对象配置,例如只关闭破折号替换而保留引号:satteri({ features: { smartPunctuation: { dashes: false } } })markdown.test.ts 验证了 GFM 与智能标点的默认开启、
gfm: false/smartypants: false时自动链接与弯引号消失、以及细粒度dashes: false时--得以保留等行为。由于这些特性现在由处理器(而不是顶层配置键)承担,旧的
markdown.gfm、markdown.smartypants、remarkPlugins、rehypePlugins、remarkRehype已被标记为弃用。校验层(validate.ts)的逻辑是:只有当你选择的处理器是unified(来自@astrojs/markdown-remark)时才会执行旧的 remark/rehype 插件;在 Sätteri 或其他第三方处理器下使用这些旧键会触发迁移警告,提示要么迁移到unified({...}),要么把插件直接传给对应处理器。七、以编程方式修改 Frontmatter(0.3.1)
0.3.1 引入了一个面向文档作者与主题作者都极具价值的特性:插件可以读取并修改页面 Frontmatter。此后,Sätteri 插件可以访问并变更
ctx.data.astro.frontmatter,Astro 会以修改后的结果作为页面最终的 Frontmatter——该行为对.md与.mdx均生效。数据由
SatteriAstroData承载(定义于 satteri-processor.ts),包含四个字段:字段 类型 说明 frontmatterRecord<string, any>页面 Frontmatter,插件可读写 headingsMarkdownHeading[]收集到的标题元数据(含 depth/slug/text)localImagePathsSet<string>渲染中出现的本地图片路径 remoteImagePathsSet<string>通过 image.domains/remotePatterns白名单校验的远程图片路径下面的示例在标题节点处向 Frontmatter 注入一个从
title派生的大写关键字(形似 inject 测试用例):// astro.config.mjs import { satteri } from '@astrojs/markdown-satteri'; const injectKeyword = { name: 'inject-keyword', heading(_node, ctx) { const astro = ctx.data.astro; astro.frontmatter.keyword = String(astro.frontmatter.title).toUpperCase(); }, }; export default defineConfig({ markdown: { processor: satteri({ mdastPlugins: [injectKeyword] }), }, });MarkdownProcessor接口本身并不具备数据总线,Sätteri 通过 TypeScript 模块增强把astro数据挂到satteri的DataMap上(见 satteri-processor.ts),使插件在ctx.data.astro获得类型安全的访问。渲染收尾时,处理器读取的是返回包(data.astro)而非最初种子化的引用,这样即使某个插件整体替换了ctx.data.astro也能被正确采用(见 satteri-processor.ts)。八、标题 ID 与
headings元数据:幂等与去重(0.3.2)标题锚点与目录元数据是内容站点的刚需。Sätteri 内置的
heading-idshast 插件(createHeadingIdsPlugin)负责:- 过滤
h1–h6; - 用
github-slugger生成 slug; - 若元素已带自定义
id(例如被其他 hast 插件先行设置),则尊重该id而非重新 slug; - 把
{ depth, slug, text }压入独立的标题数组,并回写到astro.headings。
由于标题数组在插件工厂外部声明、slugger 在多次调用间保持状态,整个插件是幂等的。0.3.2 修复的正是这一场景的边界:当某个集成(如 Starlight)在其自身的标题 pass 中先分配了 heading ID、随后又要添加锚点链接时,标题会重复出现在页面
headings元数据里——修复后无论satteriHeadingIdsPlugin()被内部默认执行还是被用户再次显式加入 hastPlugins,元数据都只收集一份。测试断言了重复标题的 slug 行为(同一标题第二次出现得到
some-text-1,见 markdown.test.ts),以及“用户插件先设置id: 'custom-id',则 DOM 输出与headings元数据都使用该自定义 ID”(见同文件 L97-L113)。该插件的工厂satteriHeadingIdsPlugin()也被导出,方便用户在自定义 hast 插件序列中显式安排其顺序。九、MDX 编译内置:
unified()与satteri()双双支持.mdx(0.4.0)0.4.0 是该包迄今最重要的能力扩展:
unified()与satteri()两个处理器都开始自行编译.mdx文件,不再依赖外部 MDX 编译链路。也就是说,.md与.mdx可以走同一条处理器声明;不过要真正给项目启用 MDX 支持,仍需安装@astrojs/mdx集成(其中包含页面扩展名注册与 Vite 插件等内容)。MDX 编译路径实现在 mdx/create-processor.ts 的
createSatteriMdxProcessor中,它调用 Sätteri 的mdxToJs编译出 JSX 代码,随后在 JS 层做若干关键加工:- 图片组件化:把
<img>转换为astro-image并在有图时注入import { Image } from "astro:assets"与具体资源导入,同时导出__usesAstroImage标志;这样用户export const components = { img: ... }的覆盖仍被尊重; - Frontmatter 输出:校验修改后的 Frontmatter 必须是合法对象(否则抛出明确错误),并生成
export const frontmatter = ...与export function getHeadings(); - Layout 包装:若 Frontmatter 含
layout,则自动把默认导出改写为用astro/jsx-runtime渲染 layout,并传入file、url、frontmatter、headings、children; - 字符集兜底:无 layout 的默认 MDX 页面自动在顶层包一层带
charset="utf-8"的 Fragment(相关判断逻辑见mdx/charset.ts); - 静态优化:透传
mdx.optimize(含ignoreElementNames),将可静态化内容编译为set:html形式的Fragment。
在特性合并上,MDX 处理器遵循“
.mdx更具体的gfm/smartPunctuation配置优先、仅作用于布尔值时”的规则,见 create-processor.ts。MDX 集成的侧翼文件(如 packages/integrations/mdx/src/index.ts)说明:markdown.processor默认覆盖到.mdx文件,extendMarkdownConfig: false时会回退到一份干净的satteri();弃用的recmaPlugins等选项只在处理器为unified时生效,否则会被忽略并告警。十、mdast/hast 插件机制与内置插件导出
Sätteri 处理器的强大之处在于保留了完整的 AST 插件扩展点。
satteri()接受mdastPlugins与hastPlugins两套插件列表,条目支持“单插件”或“[插件, 选项] 二元组”,甚至可以传入条件工厂:工厂接收ctx后按ctx.sourceFormat === 'markdown'等条件返回插件或null(见 条件工厂测试)。插件在管线中的执行顺序由渲染器编排,见 createSatteriMarkdownProcessor:
用户 mdast 插件 → collect-images(最后收集,保证用户插件改写过的图片 URL 被计入) (hast 阶段)highlight(若有高亮)→ 用户 hast 插件 → image-marker → heading-ids把图片收集放在最后是有意为之——注释明确写道“最后收集以捕获用户插件对图片 URL 的重写”,对应测试 markdown.test.ts L115-L129(
./unresolved.png被用户插件改写为./resolved.png后,元数据中记录的是改写后的路径)。包的公共 API(见 index.ts)将下列助手一并导出,供集成方在自定义管线中复用:
导出 说明 satteri()处理器工厂 isSatteriProcessor()按 name === 'satteri'判别处理器类型satteriHeadingIdsPlugin()生成标题 ID 并收集 headings satteriCollectImagesPlugin()收集本地/远程图片路径 satteriImageMarkerPlugin()给 <img>打__ASTRO_IMAGE_标记供后续图片处理satteriHighlightPlugin()语法高亮(Shiki 与 Prism 共用;旧名 satteriShikiPlugin已标记弃用)satteriCreateHighlightFn()构造高亮函数 satteriCollectHastText()递归收集 hast 文本(可解析 Frontmatter 表达式,用于标题文本计算) createSatteriMarkdownProcessor()底层 .md渲染器工厂(供测试/高级场景)十一、迁移与兼容性提示
如果你正从旧管线迁移,validate.ts 的告警与源码注释给出了明确的边界:
- 旧的
remarkPlugins/rehypePlugins/remarkRehype不再在 Sätteri 上执行。需要把它们搬到对应处理器:Remark 系插件可改用unified({ remarkPlugins })(来自@astrojs/markdown-remark)并整体作为markdown.processor;或直接传入satteri({ mdastPlugins, hastPlugins })。 - 第三方处理器无法执行 remark/rehype 插件:若
markdown.processor被设置为其他第三方实现,使用了旧插件键会收到明确告警,因为这类处理器根本没有运行 remark/rehype 的能力。 @astrojs/markdown-remark不再是默认依赖:校验代码中的报错信息提示,Sätteri 已是默认 Markdown 处理器,使用被迁移的 unified 处理器时需自行npm install @astrojs/markdown-remark。- Frontmatter 修改有硬校验:MDX 管线要求插件修改后的
ctx.data.astro.frontmatter必须是合法对象,若注入null/undefined将直接抛出带有指引的错误(见 create-processor.ts)。
结语
从 0.2.0 的“又一个可选处理器”,到 0.4.0 的“
.md/.mdx统一编译”,再到本仓库中 Astro 7.2.10 将markdown.processor默认指向satteri(),@astrojs/markdown-satteri的 CHANGELOG 记录了一次完整的技术路线落地。对读者而言,最直接的行动建议是:新项目直接使用默认配置即可获得 Sätteri 管线;需要 Prism 时切换syntaxHighlight: 'prism';需要深度定制时,通过satteri({ mdastPlugins, hastPlugins, features })在 AST 层面扩展,或利用ctx.data.astro.frontmatter在渲染期动态改写 Frontmatter。以上所有行为均可在 源码 与 单元测试 中一一验证。【免费下载链接】astroThe web framework for content-driven websites. ⭐️ Star to support our work!
项目地址: https://gitcode.com/GitHub_Trending/as/astro
- 过滤
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考