[MDX] Support remark and rehype plugins, with defaults (#3977)
* feaet: allow remark and rehype plugin config * deps: add remark-gfm, remark-smartypants * feat: add gfm and smartypants by default * test: add GFM and remark plugin tests * feat: preserve default plugins with "extends" * docs: add remarkPlugins * docs: add rehypePlugins * chore: changeset * fix: remove skip from mdx tests * chore: dup hyperlink flavor text * chore: authGen -> autoGen * nit: markdown -> Markdown Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> * nit: markdown -> Markdown 1 Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> * nit: markdown -> Markdown 2 Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> * nit: markdown -> Markdown 3 Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> * nit: markdown -> Markdown 4 Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
parent
d50f46bfab
commit
19433eb4a4
8 changed files with 217 additions and 6 deletions
5
.changeset/new-coats-cheer.md
Normal file
5
.changeset/new-coats-cheer.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/mdx': minor
|
||||
---
|
||||
|
||||
Add remarkPlugins and rehypePlugins to config, with the same default plugins as our standard Markdown parser
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
@ -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"
|
||||
|
|
|
@ -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: {
|
||||
|
@ -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
|
||||
|
|
3
packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx
vendored
Normal file
3
packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# GitHub-flavored Markdown test
|
||||
|
||||
This should auto-gen a link: https://example.com
|
19
packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx
vendored
Normal file
19
packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx
vendored
Normal file
|
@ -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
packages/integrations/mdx/test/mdx-remark-plugins.test.js
Normal file
58
packages/integrations/mdx/test/mdx-remark-plugins.test.js
Normal file
|
@ -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;
|
||||
});
|
||||
});
|
|
@ -2101,9 +2101,14 @@ importers:
|
|||
es-module-lexer: ^0.10.5
|
||||
linkedom: ^0.14.12
|
||||
mocha: ^9.2.2
|
||||
remark-gfm: ^3.0.1
|
||||
remark-smartypants: ^2.0.0
|
||||
remark-toc: ^8.0.1
|
||||
dependencies:
|
||||
'@mdx-js/rollup': 2.1.2
|
||||
es-module-lexer: 0.10.5
|
||||
remark-gfm: 3.0.1
|
||||
remark-smartypants: 2.0.0
|
||||
devDependencies:
|
||||
'@types/chai': 4.3.1
|
||||
'@types/mocha': 9.1.1
|
||||
|
@ -2113,6 +2118,7 @@ importers:
|
|||
chai: 4.3.6
|
||||
linkedom: 0.14.12
|
||||
mocha: 9.2.2
|
||||
remark-toc: 8.0.1
|
||||
|
||||
packages/integrations/netlify:
|
||||
specifiers:
|
||||
|
@ -7995,6 +8001,10 @@ packages:
|
|||
'@types/node': 18.0.3
|
||||
dev: true
|
||||
|
||||
/@types/extend/3.0.1:
|
||||
resolution: {integrity: sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==}
|
||||
dev: true
|
||||
|
||||
/@types/github-slugger/1.3.0:
|
||||
resolution: {integrity: sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==}
|
||||
dev: true
|
||||
|
@ -12029,7 +12039,19 @@ packages:
|
|||
|
||||
/mdast-util-to-string/3.1.0:
|
||||
resolution: {integrity: sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==}
|
||||
dev: false
|
||||
|
||||
/mdast-util-toc/6.1.0:
|
||||
resolution: {integrity: sha512-0PuqZELXZl4ms1sF7Lqigrqik4Ll3UhbI+jdTrfw7pZ9QPawgl7LD4GQ8MkU7bT/EwiVqChNTbifa2jLLKo76A==}
|
||||
dependencies:
|
||||
'@types/extend': 3.0.1
|
||||
'@types/github-slugger': 1.3.0
|
||||
'@types/mdast': 3.0.10
|
||||
extend: 3.0.2
|
||||
github-slugger: 1.4.0
|
||||
mdast-util-to-string: 3.1.0
|
||||
unist-util-is: 5.1.1
|
||||
unist-util-visit: 3.1.0
|
||||
dev: true
|
||||
|
||||
/mdurl/1.0.1:
|
||||
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
|
||||
|
@ -14029,6 +14051,14 @@ packages:
|
|||
unist-util-visit: 4.1.0
|
||||
dev: false
|
||||
|
||||
/remark-toc/8.0.1:
|
||||
resolution: {integrity: sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-toc: 6.1.0
|
||||
unified: 10.1.2
|
||||
dev: true
|
||||
|
||||
/require-directory/2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -15326,6 +15356,13 @@ packages:
|
|||
unist-util-is: 3.0.0
|
||||
dev: true
|
||||
|
||||
/unist-util-visit-parents/4.1.1:
|
||||
resolution: {integrity: sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
unist-util-is: 5.1.1
|
||||
dev: true
|
||||
|
||||
/unist-util-visit-parents/5.1.0:
|
||||
resolution: {integrity: sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==}
|
||||
dependencies:
|
||||
|
@ -15338,6 +15375,14 @@ packages:
|
|||
unist-util-visit-parents: 2.1.2
|
||||
dev: true
|
||||
|
||||
/unist-util-visit/3.1.0:
|
||||
resolution: {integrity: sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
unist-util-is: 5.1.1
|
||||
unist-util-visit-parents: 4.1.1
|
||||
dev: true
|
||||
|
||||
/unist-util-visit/4.1.0:
|
||||
resolution: {integrity: sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==}
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in a new issue