MDX Plugins
Sometimes, you may want to extend or tweak your Markdown syntax. Por exemplo:
- How do I embed youtube videos using the image syntax (
![](https://youtu.be/yKNxeF4KMsY)
)? - How do I style links that are on their own lines differently, e.g., as a social card?
- How do I make every page start with a copyright notice?
And the answer is: create an MDX plugin! MDX has a built-in plugin system that can be used to customize how the Markdown files will be parsed and transformed to JSX. There are three typical use-cases of MDX plugins:
- Using existing remark plugins or rehype plugins;
- Creating remark/rehype plugins to transform the elements generated by existing MDX syntax;
- Creating remark/rehype plugins to introduce new syntaxes to 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 plugins also come in two forms:
Use plugins to introduce shorter syntax for the most commonly used JSX elements in your project. The admonition syntax that we offer is also generated by a Remark plugin, and you could do the same for your own use case.
Default plugins
Docusaurus injects some default Remark plugins during Markdown processing. These plugins would:
- Generate the table of contents;
- Add anchor links to each heading;
- Transform images and links to
require()
calls. - …
These are all typical use-cases of Remark plugins, which can also be a source of inspiration if you want to implement your own plugin.
Installing plugins
An MDX plugin is usually an npm package, so you install them like other npm packages using npm. Take the math plugins as an example.
- 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 ES Modules only, a JavaScript module system, which Docusaurus supports. We recommend using an ES Modules config file to make it easier to import such packages.
Next, import your plugins and add them to the plugin options through plugin or preset config in docusaurus.config.js
:
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
export default {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
path: 'docs',
remarkPlugins: [remarkMath],
rehypePlugins: [rehypeKatex],
},
},
],
],
};
Using a CommonJS config file?
If you decide to use a CommonJS config file, it is possible to load those ES module plugins thanks to dynamic imports and an async config creator function:
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],
},
},
],
],
};
};
Configuring plugins
Some plugins can be configured and accept their own options. In that case, use the [plugin, pluginOptions]
syntax, like this:
import rehypeKatex from 'rehype-katex';
export default {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
rehypePlugins: [
[rehypeKatex, {strict: false}],
],
},
},
],
],
};
You should check your plugin's documentation for the options it supports.
Creating new rehype/remark plugins
If there isn't an existing package that satisfies your customization need, you can create your own MDX plugin.
For example, let's make a plugin that visits every h2
heading and adds a Section X.
prefix. First, create your plugin source file anywhere—you can even publish it as a separate npm package and install it like explained above. We would put ours at src/remark/section-prefix.js
. A remark/rehype plugin is just a function that receives the options
and returns a transformer
that operates on the AST.
import {visit} from '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;
};
export default plugin;
You can now import your plugin in docusaurus.config.js
and use it just like an installed plugin!
import sectionPrefix from './src/remark/section-prefix';
export default {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
remarkPlugins: [sectionPrefix],
},
},
],
],
};
The transformer
has a second parameter vfile
which is useful if you need to access the current Markdown file's path.
const plugin = (options) => {
const transformer = async (ast, vfile) => {
ast.children.unshift({
type: 'text',
value: `The current file path is ${vfile.path}`,
});
};
return transformer;
};
Our transformImage
plugin uses this parameter, for example, to transform relative image references to require()
calls.
The default plugins of Docusaurus would operate before the custom remark plugins, and that means the images or links have been converted to JSX with require()
calls already. For example, in the example above, the table of contents generated is still the same even when all h2
headings are now prefixed by Section X.
, because the TOC-generating plugin is called before our custom plugin. If you need to process the MDAST before the default plugins do, use the beforeDefaultRemarkPlugins
and beforeDefaultRehypePlugins
.
export default {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
beforeDefaultRemarkPlugins: [sectionPrefix],
},
},
],
],
};
This would make the table of contents generated contain the Section X.
prefix as well.