From 8f8dff4d339a3a12ee155d81a97132032ef3b622 Mon Sep 17 00:00:00 2001 From: Ben Holmes Date: Tue, 30 Aug 2022 13:38:35 -0400 Subject: [PATCH] [MDX] Extend Markdown plugin config, with customization options (#4504) * test: new combined remark / rehype suite * fix: use with-plugins fixture * chore: remove old mdx plugin tests * docs: add JS docs * docs: update README with thorough example * chore: changeset * fix: add "extends" error message * fix: ignore string-based plugins in md * feat: add warning log for string plugins * docs: highlight `extendPlugins` Co-authored-by: Sarah Rainsberger * nit: highlight "extendPlugins" * fix: md plugins type check * chore: "defaults" -> "astroDefaults" * nit: info log when inheriting markdown plugins * refactor: one big log on new behavior * dan: dan nit Co-authored-by: Sarah Rainsberger --- .changeset/ten-walls-listen.md | 5 + packages/integrations/mdx/README.md | 81 +++++-- packages/integrations/mdx/package.json | 1 + packages/integrations/mdx/src/index.ts | 94 +++----- packages/integrations/mdx/src/utils.ts | 127 ++++++++++- .../src/pages/with-plugins.mdx} | 0 .../src/pages/reading-time.json.js | 7 - .../src/pages/space-ipsum.mdx | 25 --- .../src/pages/headings-glob.json.js | 6 - .../mdx-remark-plugins/src/pages/with-gfm.mdx | 3 - .../integrations/mdx/test/mdx-plugins.test.js | 206 ++++++++++++++++++ .../mdx/test/mdx-rehype-plugins.test.js | 70 ------ .../mdx/test/mdx-remark-plugins.test.js | 62 ------ pnpm-lock.yaml | 2 + 14 files changed, 435 insertions(+), 254 deletions(-) create mode 100644 .changeset/ten-walls-listen.md rename packages/integrations/mdx/test/fixtures/{mdx-remark-plugins/src/pages/with-toc.mdx => mdx-plugins/src/pages/with-plugins.mdx} (100%) delete mode 100644 packages/integrations/mdx/test/fixtures/mdx-rehype-plugins/src/pages/reading-time.json.js delete mode 100644 packages/integrations/mdx/test/fixtures/mdx-rehype-plugins/src/pages/space-ipsum.mdx delete mode 100644 packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/headings-glob.json.js delete mode 100644 packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx create mode 100644 packages/integrations/mdx/test/mdx-plugins.test.js delete mode 100644 packages/integrations/mdx/test/mdx-rehype-plugins.test.js delete mode 100644 packages/integrations/mdx/test/mdx-remark-plugins.test.js diff --git a/.changeset/ten-walls-listen.md b/.changeset/ten-walls-listen.md new file mode 100644 index 000000000000..24b0acb4f2e3 --- /dev/null +++ b/.changeset/ten-walls-listen.md @@ -0,0 +1,5 @@ +--- +'@astrojs/mdx': minor +--- + +Introduce new `extendPlugins` configuration option. This defaults to inheriting all remark and rehype plugins from your `markdown` config, with options to use either Astro's defaults or no inheritance at all. diff --git a/packages/integrations/mdx/README.md b/packages/integrations/mdx/README.md index 93c8420e6b9b..e559223d5155 100644 --- a/packages/integrations/mdx/README.md +++ b/packages/integrations/mdx/README.md @@ -354,13 +354,9 @@ export default { ### 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: +This example applies the [`remark-toc`](https://github.com/remarkjs/remark-toc) plugin to `.mdx` files. To customize plugin inheritance from your Markdown config or Astro's defaults, [see the `extendPlugins` option](#extendPlugins). ```js // astro.config.mjs @@ -368,41 +364,92 @@ import remarkToc from 'remark-toc'; export default { integrations: [mdx({ - // apply remark-toc alongside GitHub-flavored markdown and Smartypants - remarkPlugins: { extends: [remarkToc] }, + remarkPlugins: [remarkToc], })], } ``` -To apply plugins _without_ Astro's defaults, you can apply a plain array: +### rehypePlugins + +[Rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) allow you to transform the HTML that your Markdown generates. We encourage you to browse [awesome-rehype](https://github.com/rehypejs/awesome-rehype) for a full curated list of plugins! + +We apply our own (non-removable) [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts) plugin. This applies IDs to all headings (i.e. `h1 -> h6`) in your MDX files to [link to headings via anchor tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#linking_to_an_element_on_the_same_page). + +This example applies the [`rehype-minify`](https://github.com/rehypejs/rehype-minify) plugin to `.mdx` files. To customize plugin inheritance from your Markdown config or Astro's defaults, [see the `extendPlugins` option](#extendPlugins). ```js // astro.config.mjs -import remarkToc from 'remark-toc'; +import rehypeMinifyHtml from 'rehype-minify'; export default { integrations: [mdx({ - // apply remark-toc alone, removing other defaults - remarkPlugins: [remarkToc], + rehypePlugins: [rehypeMinifyHtml], })], } ``` -### rehypePlugins +### extendPlugins -[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! +**Type:** `'markdown' | 'astroDefaults' | false` -We apply our own (non-overridable) [`collect-headings`](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/src/rehype-collect-headings.ts) plugin. This applies IDs to all headings (i.e. `h1 -> h6`) in your MDX files to [link to headings via anchor tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#linking_to_an_element_on_the_same_page). +**Default:** `'markdown'` -To apply additional rehype plugins, pass an array to the `rehypePlugins` option like so: +#### `markdown` (default) + +By default, Astro inherits all [remark](#remarkPlugins) and [rehype](#rehypePlugins) plugins from [the `markdown` option in your Astro config](https://docs.astro.build/en/guides/markdown-content/#markdown-plugins). This also respects the [`markdown.extendDefaultPlugins`](https://docs.astro.build/en/reference/configuration-reference/#markdownextenddefaultplugins) option to extend Astro's defaults. Any additional plugins you apply in your MDX config will be applied _after_ your configured Markdown plugins. + +This example applies [`remark-toc`](https://github.com/remarkjs/remark-toc) to Markdown _and_ MDX, and [`rehype-minify`](https://github.com/rehypejs/rehype-minify) to MDX alone: ```js // astro.config.mjs -import rehypeMinifyHtml from 'rehype-minify'; +import remarkToc from 'remark-toc'; +import rehypeMinify from 'rehype-minify'; export default { + markdown: { + // Applied to .md and .mdx files + remarkPlugins: [remarkToc], + }, integrations: [mdx({ - rehypePlugins: [rehypeMinifyHtml], + // Applied to .mdx files only + rehypePlugins: [rehypeMinify], + })], +} +``` + +#### `astroDefaults` + +You may _only_ want to extend [Astro's default plugins](https://docs.astro.build/en/reference/configuration-reference/#markdownextenddefaultplugins) without inheriting your Markdown config. This example will apply the default [GitHub-Flavored Markdown](https://github.com/remarkjs/remark-gfm) and [Smartypants](https://github.com/silvenon/remark-smartypants) plugins alongside [`remark-toc`](https://github.com/remarkjs/remark-toc): + +```js "extendPlugins: 'astroDefaults'" +// astro.config.mjs +import remarkToc from 'remark-toc'; + +export default { + markdown: { + remarkPlugins: [/** ignored */] + }, + integrations: [mdx({ + remarkPlugins: [remarkToc], + // Astro defaults applied + extendPlugins: 'astroDefaults', + })], +} +``` + +#### `false` + +If you don't want to extend any plugins, set `extendPlugins` to `false`: + +```js "extendPlugins: false" +// astro.config.mjs +import remarkToc from 'remark-toc'; + +export default { + integrations: [mdx({ + remarkPlugins: [remarkToc], + // Astro defaults not applied + extendPlugins: false, })], } ``` diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index 1a3288534660..a74538c0f656 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -36,6 +36,7 @@ "es-module-lexer": "^0.10.5", "github-slugger": "^1.4.0", "gray-matter": "^4.0.3", + "kleur": "^4.1.4", "rehype-raw": "^6.1.1", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1", diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index 6516e4b12193..0a2ddc8d3319 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -1,30 +1,19 @@ -import { compile as mdxCompile, nodeTypes } from '@mdx-js/mdx'; +import { compile as mdxCompile } from '@mdx-js/mdx'; import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup'; -import type { AstroConfig, AstroIntegration } from 'astro'; +import type { AstroIntegration } from 'astro'; import { parse as parseESM } from 'es-module-lexer'; -import rehypeRaw from 'rehype-raw'; -import remarkGfm from 'remark-gfm'; -import remarkSmartypants from 'remark-smartypants'; import { VFile } from 'vfile'; import type { Plugin as VitePlugin } from 'vite'; -import { rehypeApplyFrontmatterExport, remarkInitializeAstroData } from './astro-data-utils.js'; -import rehypeCollectHeadings from './rehype-collect-headings.js'; -import remarkPrism from './remark-prism.js'; -import remarkShiki from './remark-shiki.js'; -import { getFileInfo, parseFrontmatter } from './utils.js'; - -type WithExtends = T | { extends: T }; - -type MdxOptions = { - remarkPlugins?: WithExtends; - rehypePlugins?: WithExtends; -}; - -const DEFAULT_REMARK_PLUGINS: MdxRollupPluginOptions['remarkPlugins'] = [ - remarkGfm, - remarkSmartypants, -]; -const DEFAULT_REHYPE_PLUGINS: MdxRollupPluginOptions['rehypePlugins'] = []; +import { bold, blue } from 'kleur/colors'; +import { rehypeApplyFrontmatterExport } from './astro-data-utils.js'; +import { + getFileInfo, + parseFrontmatter, + handleExtendsNotSupported, + getRehypePlugins, + getRemarkPlugins, +} from './utils.js'; +import type { MdxOptions } from './utils.js'; const RAW_CONTENT_ERROR = 'MDX does not support rawContent()! If you need to read the Markdown contents to calculate values (ex. reading time), we suggest injecting frontmatter via remark plugins. Learn more on our docs: https://docs.astro.build/en/guides/integrations-guide/mdx/#inject-frontmatter-via-remark-or-rehype-plugins'; @@ -32,51 +21,32 @@ const RAW_CONTENT_ERROR = const COMPILED_CONTENT_ERROR = 'MDX does not support compiledContent()! If you need to read the HTML contents to calculate values (ex. reading time), we suggest injecting frontmatter via rehype plugins. Learn more on our docs: https://docs.astro.build/en/guides/integrations-guide/mdx/#inject-frontmatter-via-remark-or-rehype-plugins'; -function handleExtends(config: WithExtends, defaults: T[] = []): T[] { - if (Array.isArray(config)) return config; - - return [...defaults, ...(config?.extends ?? [])]; -} - -async function getRemarkPlugins( - mdxOptions: MdxOptions, - config: AstroConfig -): Promise { - let remarkPlugins = [ - // Initialize vfile.data.astroExports before all plugins are run - remarkInitializeAstroData, - ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS), - ]; - if (config.markdown.syntaxHighlight === 'shiki') { - remarkPlugins.push([await remarkShiki(config.markdown.shikiConfig)]); - } - if (config.markdown.syntaxHighlight === 'prism') { - remarkPlugins.push(remarkPrism); - } - return remarkPlugins; -} - -function getRehypePlugins( - mdxOptions: MdxOptions, - config: AstroConfig -): MdxRollupPluginOptions['rehypePlugins'] { - let rehypePlugins = [ - [rehypeRaw, { passThrough: nodeTypes }] as any, - ...handleExtends(mdxOptions.rehypePlugins, DEFAULT_REHYPE_PLUGINS), - ]; - - // getHeadings() is guaranteed by TS, so we can't allow user to override - rehypePlugins.unshift(rehypeCollectHeadings); - - return rehypePlugins; -} - export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration { return { name: '@astrojs/mdx', hooks: { 'astro:config:setup': async ({ updateConfig, config, addPageExtension, command }: any) => { addPageExtension('.mdx'); + mdxOptions.extendPlugins ??= 'markdown'; + + handleExtendsNotSupported(mdxOptions.remarkPlugins); + handleExtendsNotSupported(mdxOptions.rehypePlugins); + + // TODO: remove for 1.0. Shipping to ease migration to new minor + if ( + mdxOptions.extendPlugins === 'markdown' && + (config.markdown.rehypePlugins?.length || config.markdown.remarkPlugins?.length) + ) { + console.log( + blue(`[MDX] Now inheriting remark and rehype plugins from "markdown" config.`) + ); + console.log( + `If you applied a plugin to both your Markdown and MDX configs, we suggest ${bold( + 'removing the duplicate MDX entry.' + )}` + ); + console.log(`See "extendPlugins" option to configure this behavior.`); + } const mdxPluginOpts: MdxRollupPluginOptions = { remarkPlugins: await getRemarkPlugins(mdxOptions, config), diff --git a/packages/integrations/mdx/src/utils.ts b/packages/integrations/mdx/src/utils.ts index f5135ebc27ef..dc7879dd84a3 100644 --- a/packages/integrations/mdx/src/utils.ts +++ b/packages/integrations/mdx/src/utils.ts @@ -1,10 +1,33 @@ import type { Options as AcornOpts } from 'acorn'; -import { parse } from 'acorn'; import type { AstroConfig, SSRError } from 'astro'; import type { MdxjsEsm } from 'mdast-util-mdx'; - +import type { PluggableList } from '@mdx-js/mdx/lib/core.js'; +import type { Options as MdxRollupPluginOptions } from '@mdx-js/rollup'; +import { bold, yellow } from 'kleur/colors'; +import { nodeTypes } from '@mdx-js/mdx'; +import { parse } from 'acorn'; +import rehypeRaw from 'rehype-raw'; +import remarkGfm from 'remark-gfm'; +import remarkSmartypants from 'remark-smartypants'; +import { remarkInitializeAstroData } from './astro-data-utils.js'; +import rehypeCollectHeadings from './rehype-collect-headings.js'; +import remarkPrism from './remark-prism.js'; +import remarkShiki from './remark-shiki.js'; import matter from 'gray-matter'; +export type MdxOptions = { + remarkPlugins?: PluggableList; + rehypePlugins?: PluggableList; + /** + * Choose which remark and rehype plugins to inherit, if any. + * + * - "markdown" (default) - inherit your project’s markdown plugin config ([see Markdown docs](https://docs.astro.build/en/guides/markdown-content/#configuring-markdown)) + * - "astroDefaults" - inherit Astro’s default plugins only ([see defaults](https://docs.astro.build/en/reference/configuration-reference/#markdownextenddefaultplugins)) + * - false - do not inherit any plugins + */ + extendPlugins?: 'markdown' | 'astroDefaults' | false; +}; + function appendForwardSlash(path: string) { return path.endsWith('/') ? path : path + '/'; } @@ -14,6 +37,9 @@ interface FileInfo { fileUrl: string; } +const DEFAULT_REMARK_PLUGINS: PluggableList = [remarkGfm, remarkSmartypants]; +const DEFAULT_REHYPE_PLUGINS: PluggableList = []; + /** @see 'vite-plugin-utils' for source */ export function getFileInfo(id: string, config: AstroConfig): FileInfo { const sitePathname = appendForwardSlash( @@ -83,3 +109,100 @@ export function jsToTreeNode( }, }; } + +export async function getRemarkPlugins( + mdxOptions: MdxOptions, + config: AstroConfig +): Promise { + let remarkPlugins: PluggableList = [ + // Set "vfile.data.astro" for plugins to inject frontmatter + remarkInitializeAstroData, + ]; + switch (mdxOptions.extendPlugins) { + case false: + break; + case 'astroDefaults': + remarkPlugins = [...remarkPlugins, ...DEFAULT_REMARK_PLUGINS]; + break; + default: + remarkPlugins = [ + ...remarkPlugins, + ...(config.markdown.extendDefaultPlugins ? DEFAULT_REMARK_PLUGINS : []), + ...ignoreStringPlugins(config.markdown.remarkPlugins ?? []), + ]; + break; + } + if (config.markdown.syntaxHighlight === 'shiki') { + remarkPlugins.push([await remarkShiki(config.markdown.shikiConfig)]); + } + if (config.markdown.syntaxHighlight === 'prism') { + remarkPlugins.push(remarkPrism); + } + + remarkPlugins = [...remarkPlugins, ...(mdxOptions.remarkPlugins ?? [])]; + return remarkPlugins; +} + +export function getRehypePlugins( + mdxOptions: MdxOptions, + config: AstroConfig +): MdxRollupPluginOptions['rehypePlugins'] { + let rehypePlugins: PluggableList = [ + // getHeadings() is guaranteed by TS, so we can't allow user to override + rehypeCollectHeadings, + // rehypeRaw allows custom syntax highlighters to work without added config + [rehypeRaw, { passThrough: nodeTypes }] as any, + ]; + switch (mdxOptions.extendPlugins) { + case false: + break; + case 'astroDefaults': + rehypePlugins = [...rehypePlugins, ...DEFAULT_REHYPE_PLUGINS]; + break; + default: + rehypePlugins = [ + ...rehypePlugins, + ...(config.markdown.extendDefaultPlugins ? DEFAULT_REHYPE_PLUGINS : []), + ...ignoreStringPlugins(config.markdown.rehypePlugins ?? []), + ]; + break; + } + + rehypePlugins = [...rehypePlugins, ...(mdxOptions.rehypePlugins ?? [])]; + return rehypePlugins; +} + +function ignoreStringPlugins(plugins: any[]) { + let validPlugins: PluggableList = []; + let hasInvalidPlugin = false; + for (const plugin of plugins) { + if (typeof plugin === 'string') { + console.warn(yellow(`[MDX] ${bold(plugin)} not applied.`)); + hasInvalidPlugin = true; + } else if (Array.isArray(plugin) && typeof plugin[0] === 'string') { + console.warn(yellow(`[MDX] ${bold(plugin[0])} not applied.`)); + hasInvalidPlugin = true; + } else { + validPlugins.push(plugin); + } + } + if (hasInvalidPlugin) { + console.warn( + `To inherit Markdown plugins in MDX, please use explicit imports in your config instead of "strings." See Markdown docs: https://docs.astro.build/en/guides/markdown-content/#markdown-plugins` + ); + } + return validPlugins; +} + +// TODO: remove for 1.0 +export function handleExtendsNotSupported(pluginConfig: any) { + if ( + typeof pluginConfig === 'object' && + pluginConfig !== null && + (pluginConfig as any).hasOwnProperty('extends') + ) { + throw new Error( + `[MDX] The "extends" plugin option is no longer supported! Astro now extends your project's \`markdown\` plugin configuration by default. To customize this behavior, see the \`extendPlugins\` option instead: https://docs.astro.build/en/guides/integrations-guide/mdx/#extendplugins` + ); + } +} diff --git a/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx b/packages/integrations/mdx/test/fixtures/mdx-plugins/src/pages/with-plugins.mdx similarity index 100% rename from packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-toc.mdx rename to packages/integrations/mdx/test/fixtures/mdx-plugins/src/pages/with-plugins.mdx diff --git a/packages/integrations/mdx/test/fixtures/mdx-rehype-plugins/src/pages/reading-time.json.js b/packages/integrations/mdx/test/fixtures/mdx-rehype-plugins/src/pages/reading-time.json.js deleted file mode 100644 index e31c57983e92..000000000000 --- a/packages/integrations/mdx/test/fixtures/mdx-rehype-plugins/src/pages/reading-time.json.js +++ /dev/null @@ -1,7 +0,0 @@ -import * as exps from './space-ipsum.mdx'; - -export function get() { - return { - body: JSON.stringify(exps), - } -} diff --git a/packages/integrations/mdx/test/fixtures/mdx-rehype-plugins/src/pages/space-ipsum.mdx b/packages/integrations/mdx/test/fixtures/mdx-rehype-plugins/src/pages/space-ipsum.mdx deleted file mode 100644 index ad8ae7daa7ec..000000000000 --- a/packages/integrations/mdx/test/fixtures/mdx-rehype-plugins/src/pages/space-ipsum.mdx +++ /dev/null @@ -1,25 +0,0 @@ -# Space ipsum - -For those who have seen the Earth from space, and for the hundreds and perhaps thousands more who will, the experience most certainly changes your perspective. The things that we share in our world are far more valuable than those which divide us. - -It suddenly struck me that that tiny pea, pretty and blue, was the Earth. I put up my thumb and shut one eye, and my thumb blotted out the planet Earth. I didn’t feel like a giant. I felt very, very small. - -Science has not yet mastered prophecy. We predict too much for the next year and yet far too little for the next 10. - -## Section 2 - -We choose to go to the moon in this decade and do the other things, not because they are easy, but because they are hard, because that goal will serve to organize and measure the best of our energies and skills, because that challenge is one that we are willing to accept, one we are unwilling to postpone, and one which we intend to win. - -There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning. - -As I stand out here in the wonders of the unknown at Hadley, I sort of realize there’s a fundamental truth to our nature, Man must explore . . . and this is exploration at its greatest. - -## Section 3 - -Never in all their history have men been able truly to conceive of the world as one: a single sphere, a globe, having the qualities of a globe, a round earth in which all the directions eventually meet, in which there is no center because every point, or none, is center — an equal earth which all men occupy as equals. The airman’s earth, if free men make it, will be truly round: a globe in practice, not in theory. - -To be the first to enter the cosmos, to engage, single-handed, in an unprecedented duel with nature—could one dream of anything more? - -There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning. - -We are all connected; To each other, biologically. To the earth, chemically. To the rest of the universe atomically. diff --git a/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/headings-glob.json.js b/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/headings-glob.json.js deleted file mode 100644 index b73cd234d75d..000000000000 --- a/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/headings-glob.json.js +++ /dev/null @@ -1,6 +0,0 @@ -export async function get() { - const docs = await import.meta.glob('./*.mdx', { eager: true }); - return { - body: JSON.stringify(Object.values(docs).map(doc => doc.frontmatter)), - } -} 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 deleted file mode 100644 index bbb0e739992a..000000000000 --- a/packages/integrations/mdx/test/fixtures/mdx-remark-plugins/src/pages/with-gfm.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# GitHub-flavored Markdown test - -This should auto-gen a link: https://example.com diff --git a/packages/integrations/mdx/test/mdx-plugins.test.js b/packages/integrations/mdx/test/mdx-plugins.test.js new file mode 100644 index 000000000000..45f69116d023 --- /dev/null +++ b/packages/integrations/mdx/test/mdx-plugins.test.js @@ -0,0 +1,206 @@ +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-plugins/', import.meta.url); +const FILE = '/with-plugins/index.html'; + +describe('MDX plugins', () => { + it('supports custom remark plugins - TOC', async () => { + const fixture = await buildFixture({ + integrations: [ + mdx({ + remarkPlugins: [remarkToc], + }), + ], + }); + + const html = await fixture.readFile(FILE); + const { document } = parseHTML(html); + + expect(selectTocLink(document)).to.not.be.null; + }); + + it('supports custom rehype plugins', async () => { + const fixture = await buildFixture({ + integrations: [ + mdx({ + rehypePlugins: [rehypeExamplePlugin], + }), + ], + }); + const html = await fixture.readFile(FILE); + const { document } = parseHTML(html); + + expect(selectRehypeExample(document)).to.not.be.null; + }); + + it('extends markdown config by default', async () => { + const fixture = await buildFixture({ + markdown: { + remarkPlugins: [remarkExamplePlugin], + rehypePlugins: [rehypeExamplePlugin], + }, + integrations: [ + mdx(), + ], + }); + + const html = await fixture.readFile(FILE); + const { document } = parseHTML(html); + + expect(selectRemarkExample(document)).to.not.be.null; + expect(selectRehypeExample(document)).to.not.be.null; + }); + + it('ignores string-based plugins in markdown config', async () => { + const fixture = await buildFixture({ + markdown: { + remarkPlugins: [['remark-toc']], + }, + integrations: [ + mdx(), + ], + }); + + const html = await fixture.readFile(FILE); + const { document } = parseHTML(html); + + expect(selectTocLink(document)).to.be.null; + }); + + it('respects "extendDefaultPlugins" when extending markdown', async () => { + const fixture = await buildFixture({ + markdown: { + remarkPlugins: [remarkExamplePlugin], + rehypePlugins: [rehypeExamplePlugin], + extendDefaultPlugins: true, + }, + integrations: [ + mdx(), + ], + }); + + const html = await fixture.readFile(FILE); + const { document } = parseHTML(html); + + expect(selectRemarkExample(document)).to.not.be.null; + expect(selectRehypeExample(document)).to.not.be.null; + expect(selectGfmLink(document)).to.not.be.null; + }); + + it('extends markdown config with extendPlugins: "markdown"', async () => { + const fixture = await buildFixture({ + markdown: { + remarkPlugins: [remarkExamplePlugin], + rehypePlugins: [rehypeExamplePlugin], + }, + integrations: [ + mdx({ + extendPlugins: 'markdown', + remarkPlugins: [remarkToc], + }), + ], + }); + + const html = await fixture.readFile(FILE); + const { document } = parseHTML(html); + + expect(selectRemarkExample(document)).to.not.be.null; + expect(selectRehypeExample(document)).to.not.be.null; + expect(selectTocLink(document)).to.not.be.null; + }); + + it('extends default plugins with extendPlugins: "astroDefaults"', async () => { + const fixture = await buildFixture({ + markdown: { + // should NOT be applied to MDX + remarkPlugins: [remarkToc], + }, + integrations: [ + mdx({ + remarkPlugins: [remarkExamplePlugin], + rehypePlugins: [rehypeExamplePlugin], + extendPlugins: 'astroDefaults', + }), + ], + }); + + const html = await fixture.readFile(FILE); + const { document } = parseHTML(html); + + expect(selectGfmLink(document)).to.not.be.null; + // remark and rehype plugins still respected + expect(selectRemarkExample(document)).to.not.be.null; + expect(selectRehypeExample(document)).to.not.be.null; + // Does NOT inherit TOC from markdown config + expect(selectTocLink(document)).to.be.null; + }); + + it('does not extend default plugins with extendPlugins: false', async () => { + const fixture = await buildFixture({ + markdown: { + remarkPlugins: [remarkExamplePlugin], + }, + integrations: [ + mdx({ + remarkPlugins: [], + extendPlugins: false, + }), + ], + }); + + const html = await fixture.readFile(FILE); + const { document } = parseHTML(html); + + expect(selectGfmLink(document)).to.be.null; + expect(selectRemarkExample(document)).to.be.null; + }); +}); + +async function buildFixture(config) { + const fixture = await loadFixture({ + root: FIXTURE_ROOT, + ...config, + }); + await fixture.build(); + return fixture; +} + +function remarkExamplePlugin() { + return (tree) => { + tree.children.push({ + type: 'html', + value: '
', + }); + }; +} + +function rehypeExamplePlugin() { + return (tree) => { + tree.children.push({ + type: 'element', + tagName: 'div', + properties: { 'data-rehype-plugin-works': 'true' }, + }); + }; +} + +function selectTocLink(document) { + return document.querySelector('ul a[href="#section-1"]'); +} + +function selectGfmLink(document) { + return document.querySelector('a[href="https://handle-me-gfm.com"]'); +} + +function selectRemarkExample(document) { + return document.querySelector('div[data-remark-plugin-works]'); +} + +function selectRehypeExample(document) { + return document.querySelector('div[data-rehype-plugin-works]'); +} diff --git a/packages/integrations/mdx/test/mdx-rehype-plugins.test.js b/packages/integrations/mdx/test/mdx-rehype-plugins.test.js deleted file mode 100644 index 17430c750941..000000000000 --- a/packages/integrations/mdx/test/mdx-rehype-plugins.test.js +++ /dev/null @@ -1,70 +0,0 @@ -import mdx from '@astrojs/mdx'; - -import getReadingTime from 'reading-time'; -import { toString } from 'mdast-util-to-string'; -import { expect } from 'chai'; -import { parseHTML } from 'linkedom'; -import { jsToTreeNode } from '../dist/utils.js'; - -import { loadFixture } from '../../../astro/test/test-utils.js'; - -function rehypeReadingTime() { - return function (tree, { data }) { - const readingTime = getReadingTime(toString(tree)); - tree.children.unshift( - jsToTreeNode(`export const readingTime = ${JSON.stringify(readingTime)}`) - ); - }; -} - -const FIXTURE_ROOT = new URL('./fixtures/mdx-rehype-plugins/', import.meta.url); - -describe('MDX rehype plugins', () => { - describe('without "extends"', () => { - let fixture; - before(async () => { - fixture = await loadFixture({ - root: FIXTURE_ROOT, - integrations: [ - mdx({ - rehypePlugins: [rehypeReadingTime], - }), - ], - }); - await fixture.build(); - }); - - it('supports custom rehype plugins - reading time', async () => { - const { readingTime } = JSON.parse(await fixture.readFile('/reading-time.json')); - - expect(readingTime).to.not.be.null; - expect(readingTime.text).to.match(/^\d+ min read/); - }); - }); - - describe('with "extends"', () => { - let fixture; - before(async () => { - fixture = await loadFixture({ - root: FIXTURE_ROOT, - integrations: [ - mdx({ - rehypePlugins: { extends: [rehypeReadingTime] }, - }), - ], - }); - await fixture.build(); - }); - - it('preserves default getHeadings', async () => { - const html = await fixture.readFile('/space-ipsum/index.html'); - const { document } = parseHTML(html); - - const headings = [...document.querySelectorAll('h1, h2')]; - expect(headings.length).to.be.greaterThan(0); - for (const heading of headings) { - expect(heading.id).to.not.be.empty; - } - }); - }); -}); diff --git a/packages/integrations/mdx/test/mdx-remark-plugins.test.js b/packages/integrations/mdx/test/mdx-remark-plugins.test.js deleted file mode 100644 index dad97698971e..000000000000 --- a/packages/integrations/mdx/test/mdx-remark-plugins.test.js +++ /dev/null @@ -1,62 +0,0 @@ -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 43f956ea2408..eea2d1b43d57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2313,6 +2313,7 @@ importers: es-module-lexer: ^0.10.5 github-slugger: ^1.4.0 gray-matter: ^4.0.3 + kleur: ^4.1.4 linkedom: ^0.14.12 mdast-util-to-string: ^3.1.0 mocha: ^9.2.2 @@ -2334,6 +2335,7 @@ importers: es-module-lexer: 0.10.5 github-slugger: 1.4.0 gray-matter: 4.0.3 + kleur: 4.1.5 rehype-raw: 6.1.1 remark-frontmatter: 4.0.1 remark-gfm: 3.0.1