跳到主要内容
版本:Canary 🚧

MDX 插件

有时,您可能希望扩展或调整 Markdown 语法。例如:

  • 我如何使用图像语法嵌入 youtube 视频 (![](https://youtu.be/yKNxeF4KMsY))?
  • 我该如何设计不同的链接,例如,作为社交卡片?
  • 如何让每页都以版权声明开头?

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

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

如果您使用MDX 游乐场,您会注意到 MDX 编译在到达最终的 JSX 输出之前有两个中间步骤

AST (MDAST)和 Hypertext AST (HAST)。MDX 插件也有两种形式:

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

使用插件为项目中最常用的 JSX 元素引入更短的语法。我们提供的[warning](./markdown-features- warnings .mdx)语法也是由 Remark 插件生成的,你也可以为自己的用例做同样的事情。

默认插件

Docusaurus 在 Markdown 处理过程中注入一些默认的 Remark 插件。这些插件将:

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

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

安装插件

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

npm install --save remark-math@5 rehype-katex@6
How are remark-math and rehype-katex different?

In case you are wondering how Remark and Rehype are different, here is a good example. remark-math operates on the Markdown AST, where it sees text like $...$, and all it does is transform that to the JSX <span class="math math-inline">...</span> without doing too much with the content. This decouples the extraction of math formulae from their rendering, which means you can swap KaTeX\KaTeX out with other math renderers, like MathJax (with rehype-mathjax), just by replacing the Rehype plugin.

Next, the rehype-katex operates on the Hypertext AST where everything has been converted to HTML-like tags already. It traverses all the elements with math class and uses KaTeX\KaTeX to parse and render the content to actual HTML.

警告

许多官方的 Remark/Rehype 插件都使用了 ES Modules,这是一种新的 JavaScript 模块系统,Docusaurus 还不支持。为了解决这个问题,我们建议在 async 配置创建函数中使用动态的 import()。

接下来,通过docusaurus.config.js中的pluginpreset将它们添加到插件选项中,使用动态的import():

docusaurus.config.js
module.exports = async function createConfigAsync() {
return {
presets: [
[
"@docusaurus/preset-classic",
{
docs: {
path: "docs",
remarkPlugins: [(await import("remark-math")).default],
rehypePlugins: [(await import("rehype-katex")).default],
},
},
],
],
};
};

配置插件

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

docusaurus.config.js
module.exports = async function createConfigAsync() {
return {
presets: [
[
"@docusaurus/preset-classic",
{
docs: {
rehypePlugins: [
[(await import("rehype-katex")).default, { strict: false }],
],
},
},
],
],
};
};

您应该查看插件的文档,了解它支持的选项。

创建新的 rehype/remark 插件

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

备注

下面的文章并不是一个创建插件的全面指南,而只是一个如何让它与 Docusaurus 一起工作的说明。请访问RemarkRehype文档,以获得有关它们如何工作的更深入的解释。

例如,让我们制作一个插件,访问每个h2标题并添加Section X.前缀。 首先,在任何地方创建你的插件源文件——你甚至可以把它作为一个单独的 npm 包发布,然后像上面解释的那样安装它。 我们会把我们的放到src/remark/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: `The current file path is ${vfile.path}`,
});
};
return transformer;
};

例如,我们的transformImage插件使用此参数将相对图像引用转换为require()调用。

备注

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

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

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