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

)? - 怎么对单独处于一行的链接添加不同的样式,比如把它变成社交卡片的样子?
- 怎么让每一页的开头都包含一个版权声明?
答案是:做一个 MDX 插件! MDX 内置了一个插件系统,可以用来自定义 Markdown 文件是如何被如何解析并转换到 JSX 的。 MDX 插件有三种典型使用场景:
- Using existing remark plugins or rehype plugins;
- 创建 Remark/Rehype 插件来转换现有的 MDX 语法生成的元素;
- 创建 Remark/Rehype 插件来引入新的 MDX 语法。
If you play with the MDX playground, you would notice that the MDX transpilation has two intermediate steps: Markdown AST (MDAST), and Hypertext AST (HAST), before arriving at the final JSX output. MDX 插件也有两种形式:
你可以用插件来为项目中最常用的 JSX 元素创建简短的语法。 我们提供的告示语法也是由一个 Remark 插件生成的。你可以根据自己的用途仿照着来做。
默认插件
Docusaurus 会在处理 Markdown 时插入一些默认的 Remark 插件。 这些插件会:
- 生成目录;
- 给每个标题添加 ID;
- 把图像和链接转化为
require()
导入。 - ……
这些都是 Remark 插件的典型使用场景。如果你想要实现自己的插件,它们也可以成为你的灵感来源。
安装插件
每个 MDX 插件一般是一个 npm 包,所以你可以像其他包一样通过 npm 安装。 以公式插件为例。
- npm
- Yarn
- pnpm
npm install --save remark-math@5 rehype-katex@6
yarn add remark-math@5 rehype-katex@6
pnpm add 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 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 to parse and render the content to actual HTML.
Many official Remark/Rehype plugins are using ES Modules, a new JavaScript module system, which Docusaurus doesn't support yet. To work around this issue, we recommend to use dynamic import()
inside an async
config creation function.
Next, add them to the plugin options through plugin or preset config in docusaurus.config.js
, using dynamic import()
:
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]
的语法,就像这样:
module.exports = async function createConfigAsync() {
return {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
rehypePlugins: [
[(await import('rehype-katex')).default, {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.
的前缀。