MDX 插件
有时候,你可能想要扩展或者修改你的 Markdown 语法。 举个例子:
- 怎么用图像语法嵌入 YouTube 视频 (

)? - 怎么对单独处于一行的链接添加不同的样式,比如把它变成社交卡片的样子?
- 怎么让每一页的开头都包含一个版权声明?
答案是:做一个 MDX 插件! MDX 内置了一个插件系统,可以用来自定义 Markdown 文件是如何被如何解析并转换到 JSX 的。 MDX 插件有三种典型使用场景:
- Using existing remark plugins or rehype plugins;
- 创建 Remark/Rehype 插件来转换现有的 MDX 语法生成的元素;
- 创建 Remark/Rehype 插件来引入新的 MDX 语法。
如果你在 MDX 实时编辑器上尝试过,你就会注意到 MDX 转译包括了两个中间步骤: Markdown AST (MDAST), 和超文本 AST (HAST),然后才会到达最终的 JSX 输出。 MDX 插件也有两种形式:
你可以用插件来为项目中最常用的 JSX 元素创建简短的语法。 我们提供的告示语法也是由一个 Remark 插件生成的。你可以根据自己的用途仿照着来做。
默认插件
Docusaurus 会在处理 Markdown 时插入一些默认的 Remark 插件。 这些插件会:
- 生成目录;
- 给每个标题添加 ID;
- 把图像和链接转化为
require()
导入。 - ……
这些都是 Remark 插件的典型使用场景。如果你想要实现自己的插件,它们也可以成为你的灵感来源。
安装插件
每个 MDX 插件一般是一个 npm 包,所以你可以像其他包一样通过 npm 安装。 以公式插件为例。
- npm
- Yarn
- pnpm
npm install --save [email protected] [email protected]
yarn add [email protected] [email protected]
pnpm add [email protected] [email protected]
最近 Remark/Rehype 生态系统总体有向 ES 模块迁移的趋势,这是一个新的 JavaScript 模块系统,Docusaurus 还不支持它。 在我们正式支持 ESM 之前,请确保你安装的插件版本是 CommonJS 版本的。 或者,你可以在安装 rehype-katex
的教程中阅读关于使用动态 import()
的方案。
remark-math
和 rehype-katex
有什么不同?
如果你正在思考 Remark 和 Rehype 两者有何不同,这就是一个很好的例子。 remark-math
是在 Markdown AST 上运行的,它看到的是 $...$
这样的文本,而它做的所有工作就是把它变成 <span class="math math-inline">...</span>
这样的 JSX,但不会在内容上做任何工作。 这使得数学公式的解析及其渲染相互分离,从而使得你只需替换 Rehype 插件,就能把 换成其他渲染器,比如 MathJax(通过 rehype-mathjax
)。
接下来,rehype-katex
会在超文本 AST 上运行,此时所有的文本都已经转换为了类似 HTML 的标签。 它会遍历所有包含 math
类名的元素,然后用 解析其内容,并将其渲染成实际的 HTML。
接下来,在 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]
的语法,就像这样:
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
remarkPlugins: [math],
rehypePlugins: [
[katex, {strict: false}],
],
},
},
],
],
};
要了解你用的插件所支持的选项,请检查它的文档。
创建新的 rehype/remark 插件
如果没有现有的包可以满足自定义需要,你可以创建自己的 MDX 插件。
作为例子,我们来做一个插件,它会遍历每个 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
中导入你的插件,然后就像一个外部安装的插件一样使用它了!
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,可以使用 beforeDefaultRemarkPlugins
和 beforeDefaultRehypePlugins
。
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
beforeDefaultRemarkPlugins: [sectionPrefix],
},
},
],
],
};
这会使生成的目录也包含 Section X.
的前缀。