diff --git a/.changeset/new-coats-cheer.md b/.changeset/new-coats-cheer.md
new file mode 100644
index 000000000..12b4c0797
--- /dev/null
+++ b/.changeset/new-coats-cheer.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/mdx': minor
+---
+
+Add remarkPlugins and rehypePlugins to config, with the same default plugins as our standard Markdown parser
diff --git a/packages/integrations/mdx/README.md b/packages/integrations/mdx/README.md
index ed3e5ec86..541529f9f 100644
--- a/packages/integrations/mdx/README.md
+++ b/packages/integrations/mdx/README.md
@@ -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.
+
+ remarkPlugins
+
+**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],
+ })],
+}
+```
+
+
+
+
+ rehypePlugins
+
+**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],
+ })],
+}
+```
+
## Examples
diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json
index 9ea034634..51036b0e1 100644
--- a/packages/integrations/mdx/package.json
+++ b/packages/integrations/mdx/package.json
@@ -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"
diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts
index 140e86632..af63c4ad2 100644
--- a/packages/integrations/mdx/src/index.ts
+++ b/packages/integrations/mdx/src/index.ts
@@ -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 | { extends: T };
+
+type MdxOptions = {
+ remarkPlugins?: WithExtends;
+ rehypePlugins?: WithExtends;
+}
+
+const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
+
+function handleExtends(config: WithExtends, 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
diff --git a/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx b/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx
new file mode 100644
index 000000000..bbb0e7399
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx
@@ -0,0 +1,3 @@
+# GitHub-flavored Markdown test
+
+This should auto-gen a link: https://example.com
diff --git a/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx b/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx
new file mode 100644
index 000000000..fe9cac3ee
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx
@@ -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
diff --git a/packages/integrations/mdx/test/mdx-remark-plugins.test.js b/packages/integrations/mdx/test/mdx-remark-plugins.test.js
new file mode 100644
index 000000000..545df3174
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-remark-plugins.test.js
@@ -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;
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 38df67c29..e323b72d5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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: