Astro 建站实战 · 第 6 章:Markdown 渲染管线
/ 5 min read
第 6 章 · Markdown 渲染管线
对应文档:Markdown & MDX · Expressive Code
学习目标
- 看懂 Markdown → mdast → hast → HTML 的渲染旅程,知道插件插在哪。
- 会配置代码高亮(Expressive Code)并做主题适配。
- 能判断什么时候需要 MDX,什么时候 Markdown + 自定义插件就够。
概念:一条流水线
render(entry) 内部是条固定流水线:
Markdown 文本 → 解析成 mdast(Markdown AST) ← remark 插件在这一层(操作结构) → 转成 hast(HTML AST) ← rehype 插件在这一层(操作输出) → 代码块交给语法高亮器处理 → 序列化成 HTML所有配置集中在 astro.config.ts 的 markdown 字段:remarkPlugins、rehypePlugins、shikiConfig/高亮器,以及 extendMarkdownConfig(MDX 是否继承这些配置)。
动手实践
一个最小的 rehype 插件
插件就是「接收 AST、改 AST」的函数:
// 给所有外链加 target="_blank"function rehypeExternalLinks() { return (tree) => { tree.children.forEach((node) => { if (node.tagName === "a" && node.properties?.href?.startsWith("http")) { node.properties.target = "_blank"; node.properties.rel = "noopener"; } }); };}自定义块语法(如提示块)通常在 remark 层解析标记,再在 hast 层渲染成组件——这就是社区 GFM admonitions(> [!NOTE])的实现位置。
代码高亮:Expressive Code
import expressiveCode from "astro-expressive-code";export default defineConfig({ integrations: [expressiveCode({ themes: ["dracula", "github-light"], // 暗、亮各一 styleOverrides: { borderRadius: "4px" }, })],});它比默认的 Shiki 多给:代码块复制按钮、文件名标签、ins=/del= 行高亮、diff 帧、终端帧。双主题跟随站点暗色模式的诀窍是用 themeCssSelector 让生成的 CSS 挂在 [data-theme='dark'] 下。
MDX:何时才需要
MDX = Markdown 里可以 import 组件并直接渲染。代价是内容不再是「纯文本可移植」,构建链也更重。判断标准:交互组件需要插进文章中间才用;否则 Markdown + 插件足够。
踩坑提示(全部真实经历)
- 「反引号没解析」假象:
@tailwindcss/typography默认给行内代码::before/::after加装饰反引号content: "“,看起来像 Markdown 没渲染。修复是覆盖为content: none——本站tailwind.config.ts` 里有一行注释记录此事。遇到「渲染不对」先查 CSS 装饰,再查解析器。 - 标题锚点、外链
target这类需求别手写,官方/社区插件(autolink-headings、external-links)都是 hast 层的标准件。 - 自定义插件的调试方法:
astro.config里把 ASTconsole.dir出来——mdast 是 JSON 树,肉眼读比想象容易。 - 插件有 mdast/hast 两层之分,装错层的插件会「没效果但不报错」。
对照本站
- astro.config.ts 的
markdown段是本章全景图:自定义 processor 挂了 4 个 mdast 插件(图片解包、阅读时长、GitHub 仓库卡片、提示块)和 4 个 hast 插件(标题锚点、锚点链接、脚注角标、外链处理)——你在本站文章里看到的每一个「非原生 Markdown 效果」都对应其中一个插件。 - src/plugins/admonitions.ts:自定义提示块的双层实现——remark 层认语法,hast 层渲染成带图标的容器,配套样式在
src/styles/components/admonition.css。想写自己的第一个 Markdown 插件,照它抄。 - 代码高亮:Expressive Code 双主题(dracula / github-light),
themeCssSelector对齐[data-theme],配置在 src/site.config.ts 的expressiveCodeOptions。 - MDX:本站装了
@astrojs/mdx以备交互内容,但目前所有文章都是纯.md——实践「能不用就不用」。
练习
- 写一个 rehype 插件,给所有
h2加上 emoji 前缀。 - 开启 Expressive Code,试着用
ins/del语法高亮一版「修改前后」代码。 - 复刻一个最简 admonition:识别
> [!TIP]开头的引用块,渲染成带边框的提示容器。
遗留问题
纯静态内容站的路走通了。但如果某个局部真需要交互——搜索、切换、表单——难道要放弃零 JS?下一章:岛屿。