跳到主要内容
版本:Canary 🚧

MDX 插件

有时候,你可能想要扩展或者修改你的 Markdown 语法。 举个例子:

  • 怎么用图像语法嵌入 YouTube 视频 (![](https://youtu.be/yKNxeF4KMsY))?
  • 怎么对单独处于一行的链接添加不同的样式,比如把它变成社交卡片的样子?
  • 怎么让每一页的开头都包含一个版权声明?

答案是:做一个 MDX 插件! MDX 内置了一个插件系统,可以用来自定义 Markdown 文件是如何被如何解析并转换到 JSX 的。 MDX 插件有三种典型使用场景:

  • 使用现有的 Remark 插件或者 Rehype 插件
  • 创建 Remark/Rehype 插件来转换现有的 MDX 语法生成的元素;
  • 创建 Remark/Rehype 插件来引入新的 MDX 语法。

如果你在 MDX 实时编辑器上尝试过,你就会注意到 MDX 转译包括了两个中间步骤: Markdown AST (MDAST), 和超文本 AST (HAST),然后才会到达最终的 JSX 输出。 MDX 插件也有两种形式:

  • Remark: 处理 Markdown AST。
  • Rehype: 处理超文本 AST。
提示

你可以用插件来为项目中最常用的 JSX 元素创建简短的语法。 我们提供的告示语法也是由一个 Remark 插件生成的。你可以根据自己的用途仿照着来做。

默认插件

Docusaurus 会在处理 Markdown 时插入一些默认的 Remark 插件。 这些插件会:

  • 生成目录;
  • 给每个标题添加 ID;
  • 把图像和链接转化为 require() 导入。
  • ……

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

安装插件

每个 MDX 插件一般是一个 npm 包,所以你可以像其他包一样通过 npm 安装。 以公式插件为例。

备注

最近 Remark/Rehype 生态系统总体有向 ES 模块迁移的趋势,这是一个新的 JavaScript 模块系统,Docusaurus 还不支持它。 在我们正式支持 ESM 之前,请确保你安装的插件版本是 CommonJS 版本的。 或者,你可以在安装 rehype-katex 的教程中阅读关于使用动态 import() 的方案。

remark-mathrehype-katex 有什么不同?

如果你正在思考 Remark 和 Rehype 两者有何不同,这就是一个很好的例子。 remark-math 是在 Markdown AST 上运行的,它看到的是 $...$ 这样的文本,而它做的所有工作就是把它变成 <span class="math math-inline">...</span> 这样的 JSX,但不会在内容上做任何工作。 这使得数学公式的解析及其渲染相互分离,从而使得你只需替换 Rehype 插件,就能把 KaTeX\KaTeX 换成其他渲染器,比如 MathJax(通过 rehype-mathjax)。

接下来,rehype-katex 会在超文本 AST 上运行,此时所有的文本都已经转换为了类似 HTML 的标签。 它会遍历所有包含 math 类名的元素,然后用 KaTeX\KaTeX 解析其内容,并将其渲染成实际的 HTML。

接下来,在 docusaurus.config.js 中通过插件或预设配置把它们添加到插件选项中:

docusaurus.config.js
const math = require('remark-math');
const katex = require('rehype-katex');

module.exports = {
title: 'Docusaurus',
tagline: 'Build optimized websites quickly, focus on your content',
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
remarkPlugins: [math],
rehypePlugins: [katex],
},
},
],
],
};

配置插件

一些插件可以有自己的配置选项。 在这种情况下,要用 [plugin, pluginOptions] 的语法,就像这样:

docusaurus.config.js
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
remarkPlugins: [math],
rehypePlugins: [
[katex, {strict: false}],
],
},
},
],
],
};

要了解你用的插件所支持的选项,请检查它的文档。

创建新的 rehype/remark 插件

如果没有现有的包可以满足自定义需要,你可以创建自己的 MDX 插件。

备注

下面的教程不是一篇完整的插件创建指南,只是简要展示了这在 Docusaurus 中是如何实现的。 要深入了解它们的工作原理,请阅读 Remark 或者 Rehype 的文档。

作为例子,我们来做一个插件,它会遍历每个 h2 标题并添加一个 Section X. 前缀。 首先,你可以把你的插件源文件放在任何地方——你甚至可以把它作为一个 npm 包单独发布,然后像上文描述的一样安装。 我们会把我们的插件放在 src/comment/section-prefix.js 这里。 一个 remark/rehype 插件不过是一个函数,接收 options,并返回一个处理 AST 的 transformer 函数。

const visit = require('unist-util-visit');

const plugin = (options) => {
const transformer = async (ast) => {
let number = 1;
visit(ast, 'heading', (node) => {
if (node.depth === 2 && node.children.length > 0) {
node.children.unshift({
type: 'text',
value: `Section ${number}. `,
});
number++;
}
});
};
return transformer;
};

module.exports = plugin;

你现在可以在 docusaurus.config.js 中导入你的插件,然后就像一个外部安装的插件一样使用它了!

docusaurus.config.js
const sectionPrefix = require('./src/remark/section-prefix');

module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
remarkPlugins: [sectionPrefix],
},
},
],
],
};
提示

transformer 能接受第二个参数 vfile,如果你需要当前 Markdown 的文件路径,会很有用。

const plugin = (options) => {
const transformer = async (ast, vfile) => {
ast.children.unshift({
type: 'text',
value: `当前文件的路径是 ${vfile.path}`,
});
};
return transformer;
};

我们的 transformImage 插件就用了这个参数来把图像的相对路径转换到 require() 调用。

备注

Docusaurus 的默认插件会在自定义 Remark 插件之前运行,这意味着图像或链接已经被转换成包含 require() 的 JSX 了。 例如,在上文的例子中,即使所有 h2 标题现在都有了 Section X. 的前缀,生成的目录仍然是相同的。 因为目录生成插件是在我们的自定义插件之前调用的。 如果你需要在默认插件之前处理 MDAST,可以使用 beforeDefaultRemarkPluginsbeforeDefaultRehypePlugins

docusaurus.config.js
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
beforeDefaultRemarkPlugins: [sectionPrefix],
},
},
],
],
};

这会使生成的目录也包含 Section X. 的前缀。