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

[MDX] Support remark and rehype plugins, with defaults #3977

Merged
merged 16 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/new-coats-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/mdx': minor
---

Add remarkPlugins and rehypePlugins to config, with the same default plugins as our standard Markdown parser
60 changes: 59 additions & 1 deletion packages/integrations/mdx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,65 @@ Also check our [Astro Integration Documentation][astro-integration] for more on

## Configuration

There are currently no configuration options for the `@astrojs/mdx` integration. Please [open an issue](https://github.com/withastro/astro/issues/new/choose) if you have a compelling use case to share.
<details>
<summary><strong>remarkPlugins</strong></summary>

**Default plugins:** [remark-gfm](https://github.com/remarkjs/remark-gfm), [remark-smartypants](https://github.com/silvenon/remark-smartypants)

[Remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) allow you to extend your Markdown with new capabilities. This includes [auto-generating a table of contents](https://github.com/remarkjs/remark-toc), [applying accessible emoji labels](https://github.com/florianeckerstorfer/remark-a11y-emoji), and more. We encourage you to browse [awesome-remark](https://github.com/remarkjs/awesome-remark) for a full curated list!

We apply [GitHub-flavored Markdown](https://github.com/remarkjs/remark-gfm) and [Smartypants](https://github.com/silvenon/remark-smartypants) by default. This brings some niceties like auto-generating clickable links from text (ex. `https://example.com`) and formatting quotes for readability. When applying your own plugins, you can choose to preserve or remove these defaults.

To apply plugins _while preserving_ Astro's default plugins, use a nested `extends` object like so:

```js
// astro.config.mjs
import remarkToc from 'remark-toc';

export default {
integrations: [mdx({
// apply remark-toc alongside GitHub-flavored markdown and Smartypants
remarkPlugins: { extends: [remarkToc] },
})],
}
```

To apply plugins _without_ Astro's defaults, you can apply a plain array:

```js
// astro.config.mjs
import remarkToc from 'remark-toc';

export default {
integrations: [mdx({
// apply remark-toc alone, removing other defaults
remarkPlugins: [remarkToc],
})],
}
```

</details>

<details>
<summary><strong>rehypePlugins</strong></summary>

**Default plugins:** none

[Rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) allow you to transform the HTML that your Markdown generates. We recommend checking the [Remark plugin](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) catalog first _before_ considering rehype plugins, since most users want to transform their Markdown syntax instead. If HTML transforms are what you need, we encourage you to browse [awesome-rehype](https://github.com/rehypejs/awesome-rehype) for a full curated list of plugins!

To apply rehype plugins, use the `rehypePlugins` configuration option like so:

```js
// astro.config.mjs
import rehypeMinifyHtml from 'rehype-minify';

export default {
integrations: [mdx({
rehypePlugins: [rehypeMinifyHtml],
})],
}
```
</details>

## Examples

Expand Down
7 changes: 5 additions & 2 deletions packages/integrations/mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
},
"dependencies": {
"@mdx-js/rollup": "^2.1.1",
"es-module-lexer": "^0.10.5"
"es-module-lexer": "^0.10.5",
"remark-gfm": "^3.0.1",
"remark-smartypants": "^2.0.0"
},
"devDependencies": {
"@types/chai": "^4.3.1",
Expand All @@ -41,7 +43,8 @@
"astro-scripts": "workspace:*",
"chai": "^4.3.6",
"linkedom": "^0.14.12",
"mocha": "^9.2.2"
"mocha": "^9.2.2",
"remark-toc": "^8.0.1"
},
"engines": {
"node": "^14.18.0 || >=16.12.0"
Expand Down
24 changes: 22 additions & 2 deletions packages/integrations/mdx/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import mdxPlugin from '@mdx-js/rollup';
import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
import type { AstroIntegration } from 'astro';
import { parse as parseESM } from 'es-module-lexer';
import remarkGfm from 'remark-gfm';
import remarkSmartypants from 'remark-smartypants';
import { getFileInfo } from './utils.js';

export default function mdx(): AstroIntegration {
type WithExtends<T> = T | { extends: T };

type MdxOptions = {
remarkPlugins?: WithExtends<MdxRollupPluginOptions['remarkPlugins']>;
rehypePlugins?: WithExtends<MdxRollupPluginOptions['rehypePlugins']>;
}

const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];

function handleExtends<T>(config: WithExtends<T[] | undefined>, defaults: T[] = []): T[] | undefined {
if (Array.isArray(config)) return config;

return [...defaults, ...(config?.extends ?? [])];
}

export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
return {
name: '@astrojs/mdx',
hooks: {
Expand All @@ -15,6 +32,9 @@ export default function mdx(): AstroIntegration {
{
enforce: 'pre',
...mdxPlugin({
remarkPlugins: handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS),
rehypePlugins: handleExtends(mdxOptions.rehypePlugins),
// place these after so the user can't override
jsx: true,
jsxImportSource: 'astro',
// Note: disable `.md` support
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# GitHub-flavored Markdown test

This should auto-gen a link: https://example.com
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# TOC test

## Table of contents

## Section 1

Some text!

### Subsection 1

Some subsection test!

### Subsection 2

Oh cool, more text!

## Section 2

And section 2, with a hyperlink to check GFM is preserved: https://handle-me-gfm.com
58 changes: 58 additions & 0 deletions packages/integrations/mdx/test/mdx-remark-plugins.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import mdx from '@astrojs/mdx';

import { expect } from 'chai';
import { parseHTML } from 'linkedom';
import { loadFixture } from '../../../astro/test/test-utils.js';
import remarkToc from 'remark-toc';

const FIXTURE_ROOT = new URL('./fixtures/mdx-remark-plugins/', import.meta.url);

describe('MDX remark plugins', () => {
it('supports custom remark plugins - TOC', async () => {
const fixture = await loadFixture({
root: FIXTURE_ROOT,
integrations: [mdx({
remarkPlugins: [remarkToc],
})],
});
await fixture.build();

const html = await fixture.readFile('/with-toc/index.html');
const { document } = parseHTML(html);

const tocLink1 = document.querySelector('ul a[href="#section-1"]');
expect(tocLink1).to.not.be.null;
});

it('applies GitHub-flavored markdown by default', async () => {
const fixture = await loadFixture({
root: FIXTURE_ROOT,
integrations: [mdx()],
});
await fixture.build();

const html = await fixture.readFile('/with-gfm/index.html');
const { document } = parseHTML(html);

const autoGenLink = document.querySelector('a[href="https://example.com"]');
expect(autoGenLink).to.not.be.null;
});

it('preserves default GitHub-flavored markdown with "extends"', async () => {
const fixture = await loadFixture({
root: FIXTURE_ROOT,
integrations: [mdx({
remarkPlugins: { extends: [remarkToc] },
})],
});
await fixture.build();

const html = await fixture.readFile('/with-toc/index.html');
const { document } = parseHTML(html);

const tocLink1 = document.querySelector('ul a[href="#section-1"]');
expect(tocLink1).to.not.be.null;
const autoGenLink = document.querySelector('a[href="https://handle-me-gfm.com"]');
expect(autoGenLink).to.not.be.null;
});
});
47 changes: 46 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.