什么是 MDX?
MDX 是MarkDown的超集, 在支持MarkDown语法的基础上, 允许我们引入jsx, 以使用更丰富的组件
同时 MDX 提供了 remark 和 rehype 的插件机制, 使得可以在 MDX 中处理MarkDown标签, 使我们可以更加自由的渲染页面
什么是 remark 和 rehype?
remark 用于处理md的AST, rehype 用于处理html的AST
要想理解他们,
首先我们要了解AST(Abstract Syntax Tree): 抽象语法树
它通过树状结构描述了代码的语法结构, 使我们可以对代码进行分析和处理, 可以参考如下文件:
# 标题
```jsconst a = 1```{ "type": "root", "children": [ { "type": "heading", "depth": 1, "children": [ { "type": "text", "value": "标题" } ] }, { "type": "code", "lang": "js", "value": "const a = 1" } ]}<h1>标题</h1><pre><code class="language-js">const a = 1</code></pre>开始编写一个 remark 插件
安装好必要的依赖后, 首先在配置中引入插件 (以 astro 为例)
import { defineConfig } from "astro/config";import { unified } from "@astrojs/markdown-remark";import { pluginFunc } from "./remark-plugin.mjs";
export default defineConfig({ markdown: { processor: unified({ remarkPlugins: [pluginFunc], rehypePlugins: [], }), }})其中remark-plugin.mjs是自定义的插件
pluginFunc是一个返回(tree)=>{...}的闭包函数
其最简示例如下:
export function remarkContainer() { return (tree) => { const { children } = tree; let index = 0;
while (index < children.length) { const node = children[index];
// 寻找起始标记 :::name if ( node.type === "paragraph" && node.children.length === 1 && node.children[0].type === "text" ) { const marker = node.children[0].value.match(/^:::(\w+)\s*$/);
if (marker) { const name = marker[1]; let closeIndex = -1;
// 寻找闭合标记 for (let i = index + 1; i < children.length; i++) { const c = children[i]; if ( c.type === "paragraph" && c.children.length === 1 && c.children[0].type === "text" && c.children[0].value.trim() === ":::" ) { closeIndex = i; break; } }
// 修改内容 if (closeIndex !== -1) { const containerChildren = children.slice(index + 1, closeIndex); const newNode = { type: "newContainer", name: name, children: containerChildren, }; children.splice(index, closeIndex - index + 1, newNode); index++; continue; } } } index++; } };}重点分为两个步骤, 1.寻找标记, 2.修改内容
以上函数会将:::name ... :::打包到新的节点
代码演示
# 标题
:::warning这是**警告**:::{ "type": "root", "children": [ { "type": "heading", "depth": 1, "children": [ { "type": "text", "value": "标题" } ] }, { "type": "paragraph", "children": [ { "type": "text", "value": ":::warning" } ] }, { "type": "paragraph", "children": [ { "type": "text", "value": "一个" }, { "type": "strong", "children": [ { "type": "text", "value": "警告" } ] } ] }, { "type": "paragraph", "children": [ { "type": "text", "value": ":::" } ] } ]}{ "type": "root", "children": [ { "type": "heading", "depth": 1, "children": [ { "type": "text", "value": "标题" } ] }, { "type": "newContainer", "name": "warning", "children": [ { "type": "paragraph", "children": [ { "type": "text", "value": "一个" }, { "type": "strong", "children": [ { "type": "text", "value": "警告" } ] } ] } ] } ]}<h1>标题</h1><div class="warning"> <p>这是一个<strong>警告</strong></p></div>