跳到主要内容

MDX 插件

有时,你可能想要扩展或调整你的 Markdown 语法。例如:

  • 如何使用图片语法(![](https://youtu.be/yKNxeF4KMsY))来嵌入 YouTube 视频?
  • 如何为单独成行的链接设置不同样式,比如社交卡片样式?
  • 如何让每个页面都以一个版权声明开始?

答案是:创建一个 MDX 插件!MDX 有一个内置的插件系统,可以用来定制 Markdown 文件如何被解析并转换为 JSX。MDX 插件有三种典型用例:

  • 使用现有的 remark 插件rehype 插件
  • 创建 remark/rehype 插件来转换由现有 MDX 语法生成的元素;
  • 创建 remark/rehype 插件来为 MDX 引入新的语法。

如果你用过 MDX playground,你会注意到 MDX 的转译过程有两个中间步骤:Markdown AST (MDAST) 和 Hypertext AST (HAST),最后才生成最终的 JSX 输出。MDX 插件也分为两种形式:

  • Remark:处理 Markdown AST。
  • Rehype:处理 Hypertext AST。
提示

使用插件来为你项目中一些最常用的 JSX 元素引入更短的语法。我们提供的提示框(admonition)语法也是由一个 Remark 插件生成的,你也可以为自己的用例做同样的事情。

默认插件

道格龙(Docusaurus)在 Markdown 处理过程中注入了一些默认的 Remark 插件。这些插件会:

  • 生成目录;
  • 为每个标题添加锚链接;
  • 将图片和链接转换为 require() 调用。

这些都是 Remark 插件的典型用例,如果你想实现自己的插件,它们也可以作为灵感来源。

安装插件

一个 MDX 插件通常是一个 npm 包,所以你可以像安装其他 npm 包一样使用 npm 来安装它们。以数学公式插件为例。

npm install --save remark-math@5 rehype-katex@6
remark-mathrehype-katex 有何不同?

如果你想知道 Remark 和 Rehype 有何不同,这里有一个很好的例子。remark-math 操作的是 Markdown AST,它看到像 $...$ 这样的文本,所做的只是将其转换为 JSX <span class="math math-inline">...</span>,而不会对内容做太多处理。这使得数学公式的提取与其渲染解耦,意味着你可以通过简单地替换 Rehype 插件,将 KaTeX\KaTeX 换成其他数学渲染器,比如 MathJax(使用 rehype-mathjax)。

接下来,rehype-katex 操作的是 Hypertext AST,此时所有内容都已被转换为类似 HTML 的标签。它会遍历所有带有 math 类的元素,并使用 KaTeX\KaTeX 来解析和渲染其内容为实际的 HTML。

注意

许多官方的 Remark/Rehype 插件是纯 ES 模块(ES Modules only),这是一种道格龙(Docusaurus)支持的 JavaScript 模块系统。我们推荐使用 ES 模块配置文件,以便更容易地导入这类包。

接下来,在 docusaurus.config.js 中导入你的插件,并通过插件或预设的配置将它们添加到插件选项中:

docusaurus.config.js
// 导入 remark-math 插件
import remarkMath from 'remark-math';
// 导入 rehype-katex 插件
import rehypeKatex from 'rehype-katex';

export default {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
path: 'docs',
remarkPlugins: [remarkMath], // 注册 remark-math 插件
rehypePlugins: [rehypeKatex], // 注册 rehype-katex 插件
},
},
],
],
};
使用 CommonJS 配置文件?

如果你决定使用 CommonJS 配置文件,可以通过动态导入和异步配置创建函数来加载那些 ES 模块插件:

docusaurus.config.js
// 使用异步函数创建配置
module.exports = async function createConfigAsync() {
return {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
path: 'docs',
// 动态导入 remark-math 插件
remarkPlugins: [(await import('remark-math')).default],
// 动态导入 rehype-katex 插件
rehypePlugins: [(await import('rehype-katex')).default],
},
},
],
],
};
};

配置插件

一些插件可以被配置并接受它们自己的选项。在这种情况下,使用 [plugin, pluginOptions] 的语法,就像这样:

docusaurus.config.js
import rehypeKatex from 'rehype-katex';

export default {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
rehypePlugins: [
// 为 rehype-katex 插件传递配置选项
[rehypeKatex, {strict: false}],
],
},
},
],
],
};

你应该查阅你所用插件的文档来了解它支持哪些选项。

创建新的 rehype/remark 插件

如果现有的包不能满足你的定制需求,你可以创建自己的 MDX 插件。

备注

下面的内容并非创建插件的详尽指南,只是为了说明如何让它与道格龙(Docusaurus)一起工作。请访问 RemarkRehype 的文档,以更深入地了解它们的工作原理。

例如,让我们来创建一个插件,它会访问每一个 h2 标题并添加一个 第 X 节. 的前缀。首先,在任何地方创建你的插件源文件——你甚至可以将其发布为一个单独的 npm 包,并像上面解释的那样安装它。我们会把我们的文件放在 src/remark/section-prefix.js。一个 remark/rehype 插件只是一个接收 options 并返回一个在 AST 上操作的 transformer 的函数。

// 导入 unist-util-visit 用于遍历 AST
import {visit} from 'unist-util-visit';

// 插件函数,接收选项作为参数
const plugin = (options) => {
// 返回一个 transformer 函数,它将处理 AST
const transformer = async (ast) => {
let number = 1; // 初始化章节计数器
// 遍历 AST 中所有的 'heading' 节点
visit(ast, 'heading', (node) => {
// 检查是否是 h2 标题 (depth === 2) 且有内容
if (node.depth === 2 && node.children.length > 0) {
// 在标题内容前插入章节编号
node.children.unshift({
type: 'text',
value: `${number} 节. `,
});
number++; // 计数器加一
}
});
};
return transformer;
};

export default plugin;

现在你可以在 docusaurus.config.js 中导入你的插件,并像使用已安装的插件一样使用它!

docusaurus.config.js
// 导入我们自己创建的 remark 插件
import sectionPrefix from './src/remark/section-prefix';

export default {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
// 注册我们自定义的插件
remarkPlugins: [sectionPrefix],
},
},
],
],
};
提示

transformer 函数有第二个参数 vfile,如果你需要访问当前 Markdown 文件的路径,这个参数会很有用。

const plugin = (options) => {
// transformer 接收 ast 和 vfile 作为参数
const transformer = async (ast, vfile) => {
// 在内容开头插入当前文件的路径信息
ast.children.unshift({
type: 'text',
value: `当前文件路径是 ${vfile.path}`,
});
};
return transformer;
};

例如,我们的 transformImage 插件就使用了这个参数,来将相对图片引用转换为 require() 调用。

备注

道格龙(Docusaurus)的默认插件会在自定义的 remark 插件之前运行,这意味着图片或链接已经被转换成了带有 require() 调用的 JSX。例如,在上面的例子中,即使所有的 h2 标题现在都加上了 第 X 节. 的前缀,生成的目录仍然是相同的,因为生成目录的插件在我们的自定义插件之前被调用。如果你需要在默认插件之前处理 MDAST,请使用 beforeDefaultRemarkPluginsbeforeDefaultRehypePlugins

docusaurus.config.js
export default {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
// 在默认插件之前运行自定义插件
beforeDefaultRemarkPlugins: [sectionPrefix],
},
},
],
],
};

这样一来,生成的目录中也会包含 第 X 节. 的前缀了。