Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: elaborate on the usage of MDX plugins #5766

Merged
merged 11 commits into from
Oct 29, 2021
182 changes: 151 additions & 31 deletions website/docs/guides/markdown-features/markdown-features-plugins.mdx
Original file line number Diff line number Diff line change
@@ -1,72 +1,109 @@
---
id: plugins
title: Plugins
title: MDX Plugins
description: Using MDX plugins to expand Docusaurus Markdown functionalities
slug: /markdown-features/plugins
---

You can expand the MDX functionalities, using plugins.
Sometimes, you may want to extend or tweak your Markdown syntax. For example:

Docusaurus content plugins support both [Remark](https://github.com/remarkjs/remark) and [Rehype](https://github.com/rehypejs/rehype) plugins that work with MDX.
- How do I embed youtube videos using the image syntax (`![](https://youtu.be/yKNxeF4KMsY)`)?
- How do I style links that are on its own line differently, e.g., like a social card?
- How do I make every page start with a copyright notice?

## Configuring plugins {#configuring-plugins}
And the answer is: create an MDX plugin! MDX has a built-in [plugin system](https://mdxjs.com/advanced/plugins/) 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](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins) or [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins);
- Creating remark/rehype plugins to tranform the elements generated by existing MDX syntax;
- Creating remark/rehype plugins to introduce new syntaxes to MDX.

If you play with the [MDX playground](https://mdx-git-renovate-babel-monorepo-mdx.vercel.app/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:

- **[Remark](https://github.com/remarkjs/remark/)**: processes the Markdown AST.
- **[Rehype](https://github.com/rehypejs/rehype/)**: processes the Hypertext AST.

:::tip

Use plugins to introduce shorter syntax for the most commonly used JSX elements in your project. The [admonition](./markdown-features-admonitions.mdx) syntax that we offer is also generated by a [Remark plugin](https://github.com/elviswolcott/remark-admonitions), and you could do the same for your own use-case.

:::

## Default plugins {#default-plugins}

An MDX plugin is usually a npm package, so you install them like other npm packages using npm.
Docusaurus injects [some default Remark plugins](https://github.com/facebook/docusaurus/tree/main/packages/docusaurus-mdx-loader/src/remark) during Markdown processing. These plugins would:

First, install your [Remark](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins) and [Rehype](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins) plugins.
- Generate the table of contents;
- Add anchor links to each heading;
- Transform images and links to `require()` calls.
- …

For example:
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 {#installing-plugins}

An MDX plugin is usually a npm package, so you install them like other npm packages using npm. Take the [math plugins](./markdown-features-math-equations.mdx) as example.

```bash npm2yarn
npm install --save remark-images
npm install --save rehype-truncate
npm install --save remark-math@3 rehype-katex@4
```

Next, import the plugins:
:::note

```js
const remarkImages = require('remark-images');
const rehypeTruncate = require('rehype-truncate');
```
There's recently a trend in the Remark/Rehype ecosystem to migrate to ES Modules, which Docusaurus doesn't support yet. Please make sure your installed plugin version is CommonJS-compatible before we officially support ESM.

:::

<details>
<summary>How are <code>remark-math</code> and <code>rehype-katex</code> different?</summary>

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 transforms 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$ out with other math renderers, like MathJax (with [`rehype-mathjax`](https://github.com/remarkjs/remark-math/tree/main/packages/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$ to parse and render the content to actual HTML.

</details>

Finally, add them to the `@docusaurus/preset-classic` options in `docusaurus.config.js`:
Next, add them to the plugin options through plugin or preset config in `docusaurus.config.js`:

```js title="docusaurus.config.js"
// highlight-start
const math = require('remark-math');
const katex = require('rehype-katex');
// highlight-end

```js {10,11} title="docusaurus.config.js"
module.exports = {
// ...
title: 'Docusaurus',
tagline: 'Build optimized websites quickly, focus on your content',
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
sidebarPath: require.resolve('./sidebars.js'),
// ...
remarkPlugins: [remarkImages],
rehypePlugins: [rehypeTruncate],
// highlight-start
remarkPlugins: [math],
rehypePlugins: [katex],
// highlight-end
},
},
],
],
};
```

## Configuring plugin options {#configuring-plugin-options}
## Configuring plugins {#configuring-plugins}

Some plugins can be configured and accept their own options. In that case, use the `[plugin, pluginOptions]` syntax, like so:
Some plugins can be configured and accept their own options. In that case, use the `[plugin, pluginOptions]` syntax, like this:

```jsx {10-13} title="docusaurus.config.js"
```jsx title="docusaurus.config.js"
module.exports = {
// ...
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
sidebarPath: require.resolve('./sidebars.js'),
// ...
remarkPlugins: [
plugin1,
[plugin2, {option1: {...}}],
remarkPlugins: [math],
rehypePlugins: [
// highlight-next-line
[katex, {strict: false}],
],
},
},
Expand All @@ -75,4 +112,87 @@ module.exports = {
};
```

See more information in the [MDX documentation](https://mdxjs.com/advanced/plugins).
You should check your plugin's documentation for 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.

:::note

The writeup below is **not** meant to be a comprehensive guide to creating a plugin, but just an illustration of how to make it work with Docusaurus. Visit the [Remark](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#creating-plugins) or [Rehype](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#creating-plugins) documentation for a more in-depth explanation of how they work.

:::

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` which operates on the AST.

```js "src/remark/section-prefix.js"
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) {
if (node.children[0].type === 'text') {
node.children[0].value = `Section ${number}. ${node.children[0].value}`;
} else {
node.children.unshift({
type: 'text',
value: `Section ${number}. `,
});
}
number++;
}
});
};
return transformer;
};

module.exports = plugin;
```

You can now import your plugin in `docusaurus.config.js` and use it just like an installed plugin!

```jsx title="docusaurus.config.js"
// highlight-next-line
const sectionPrefix = require('./src/remark/section-prefix');

module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
// highlight-next-line
remarkPlugins: [sectionPrefix],
},
},
],
],
};
```

:::note

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`.

```jsx title="docusaurus.config.js"
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
// highlight-next-line
beforeDefaultRemarkPlugins: [sectionPrefix],
},
},
],
],
};
```

This would make the table of contents generated contain the `Section X.` prefix as well.

:::