diff options
Diffstat (limited to 'packages/integrations')
8 files changed, 185 insertions, 5 deletions
diff --git a/packages/integrations/mdx/README.md b/packages/integrations/mdx/README.md index 541529f9f..13eb94187 100644 --- a/packages/integrations/mdx/README.md +++ b/packages/integrations/mdx/README.md @@ -78,6 +78,62 @@ To write your first MDX page in Astro, head to our [UI framework documentation][ Also check our [Astro Integration Documentation][astro-integration] for more on integrations. +### Variables + +MDX supports `export` statements to add variables to your templates. These variables are accessible both from the template itself _and_ as named properties when importing the template somewhere else. + +For instance, you can export a `title` field from an MDX page or component to use as a heading with `{JSX expressions}`: + +```mdx +export const title = 'My first MDX post' + +# {title} +``` + +This `title` will be accessible from `import` and [glob](https://docs.astro.build/en/reference/api-reference/#astroglob) statements as well: + +```astro +--- +// src/pages/index.astro +const posts = await Astro.glob('./*.mdx'); +--- + +{posts.map(post => <p>{post.title}</p>)} +``` + +See [the official "how MDX works" guide](https://mdxjs.com/docs/using-mdx/#how-mdx-works) for more on MDX variables. + +### Frontmatter + +Astro also supports YAML-based frontmatter out-of-the-box using the [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter) plugin. By default, all variables declared in a frontmatter fence (`---`) will be accessible via the `frontmatter` export. See the `frontmatterOptions` configuration to customize this behavior. + +For example, we can add a `title` and `publishDate` to an MDX page or component like so: + +```mdx +--- +title: 'My first MDX post' +publishDate: '21 September 2022' +--- + +# {frontmatter.title} +``` + +Now, this `title` and `publishDate` will be accessible from `import` and [glob](https://docs.astro.build/en/reference/api-reference/#astroglob) statements via the `frontmatter` property. This matches the behavior of [plain markdown in Astro](https://docs.astro.build/en/reference/api-reference/#markdown-files) as well! + +```astro +--- +// src/pages/index.astro +const posts = await Astro.glob('./*.mdx'); +--- + +{posts.map(post => ( + <Fragment> + <h2>{post.frontmatter.title}</h2> + <time>{post.frontmatter.publishDate}</time> + </Fragment> +))} +``` + ## Configuration <details> @@ -140,6 +196,37 @@ export default { ``` </details> +<details> + <summary><strong>frontmatterOptions</strong></summary> + +**Default:** `{ name: 'frontmatter' }` + +We use [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter) to parse YAML-based frontmatter in your MDX files. If you want to override our default configuration or extend remark-mdx-frontmatter (ex. to [apply a custom frontmatter parser](https://github.com/remcohaszing/remark-mdx-frontmatter#parsers)), you can supply a `frontmatterOptions` configuration. + +For example, say you want to access frontmatter as root-level variables without a nested `frontmatter` object. You can override the [`name` configuration option](https://github.com/remcohaszing/remark-mdx-frontmatter#name) like so: + +```js +// astro.config.mjs +export default { + integrations: [mdx({ + frontmatterOptions: { + name: '', + } + })], +} +``` + +```mdx +--- +title: I'm just a variable now! +--- + +# {title} +``` + +See the [remark-mdx-frontmatter README](https://github.com/remcohaszing/remark-mdx-frontmatter#options) for a complete list of options. +</details> + ## Examples - The [Astro MDX example](https://github.com/withastro/astro/tree/latest/examples/with-mdx) shows how to use MDX files in your Astro project. diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index 51036b0e1..8084495f8 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -32,7 +32,9 @@ "dependencies": { "@mdx-js/rollup": "^2.1.1", "es-module-lexer": "^0.10.5", + "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1", + "remark-mdx-frontmatter": "^2.0.2", "remark-smartypants": "^2.0.0" }, "devDependencies": { diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index d2525b6e9..d91f71c4b 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -1,8 +1,11 @@ import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup'; import type { AstroIntegration } from 'astro'; +import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter'; import { parse as parseESM } from 'es-module-lexer'; import remarkGfm from 'remark-gfm'; import remarkSmartypants from 'remark-smartypants'; +import remarkFrontmatter from 'remark-frontmatter'; +import remarkMdxFrontmatter from 'remark-mdx-frontmatter'; import { getFileInfo } from './utils.js'; type WithExtends<T> = T | { extends: T }; @@ -10,14 +13,20 @@ type WithExtends<T> = T | { extends: T }; type MdxOptions = { remarkPlugins?: WithExtends<MdxRollupPluginOptions['remarkPlugins']>; rehypePlugins?: WithExtends<MdxRollupPluginOptions['rehypePlugins']>; -}; + /** + * Configure the remark-mdx-frontmatter plugin + * @see https://github.com/remcohaszing/remark-mdx-frontmatter#options for a full list of options + * @default {{ name: 'frontmatter' }} + */ + frontmatterOptions?: RemarkMdxFrontmatterOptions; +} const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants]; function handleExtends<T>( config: WithExtends<T[] | undefined>, - defaults: T[] = [] -): T[] | undefined { + defaults: T[] = [], +): T[] { if (Array.isArray(config)) return config; return [...defaults, ...(config?.extends ?? [])]; @@ -35,9 +44,18 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration { { enforce: 'pre', ...mdxPlugin({ - remarkPlugins: handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS), + remarkPlugins: [ + ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS), + // Frontmatter plugins should always be applied! + // We can revisit this if a strong use case to *remove* + // YAML frontmatter via config is reported. + remarkFrontmatter, + [remarkMdxFrontmatter, { + name: 'frontmatter', + ...mdxOptions.frontmatterOptions, + }], + ], 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-custom-frontmatter-name/src/pages/glob.json.js b/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js new file mode 100644 index 000000000..2f8155ada --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js @@ -0,0 +1,9 @@ +export async function get() { + const mdxPages = await import.meta.glob('./*.mdx', { eager: true }); + + return { + body: JSON.stringify({ + titles: Object.values(mdxPages ?? {}).map(v => v?.customFrontmatter?.title), + }) + } +} diff --git a/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx new file mode 100644 index 000000000..e3c789149 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx @@ -0,0 +1,6 @@ +--- +title: 'Using YAML frontmatter' +illThrowIfIDontExist: "Oh no, that's scary!" +--- + +# {customFrontmatter.illThrowIfIDontExist} diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js new file mode 100644 index 000000000..a1d6a4ac4 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js @@ -0,0 +1,9 @@ +export async function get() { + const mdxPages = await import.meta.glob('./*.mdx', { eager: true }); + + return { + body: JSON.stringify({ + titles: Object.values(mdxPages ?? {}).map(v => v?.frontmatter?.title), + }) + } +} diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx new file mode 100644 index 000000000..333026f85 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx @@ -0,0 +1,6 @@ +--- +title: 'Using YAML frontmatter' +illThrowIfIDontExist: "Oh no, that's scary!" +--- + +# {frontmatter.illThrowIfIDontExist} diff --git a/packages/integrations/mdx/test/mdx-frontmatter.js b/packages/integrations/mdx/test/mdx-frontmatter.js new file mode 100644 index 000000000..1d8ec36f6 --- /dev/null +++ b/packages/integrations/mdx/test/mdx-frontmatter.js @@ -0,0 +1,43 @@ +import mdx from '@astrojs/mdx'; + +import { expect } from 'chai'; +import { loadFixture } from '../../../astro/test/test-utils.js'; + +const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter/', import.meta.url); + +describe('MDX frontmatter', () => { + it('builds when "frontmatter.property" is in JSX expression', async () => { + const fixture = await loadFixture({ + root: FIXTURE_ROOT, + integrations: [mdx()], + }); + await fixture.build(); + expect(true).to.equal(true); + }); + + it('extracts frontmatter to "frontmatter" export', async () => { + const fixture = await loadFixture({ + root: FIXTURE_ROOT, + integrations: [mdx()], + }); + await fixture.build(); + + const { titles } = JSON.parse(await fixture.readFile('/glob.json')); + expect(titles).to.include('Using YAML frontmatter'); + }); + + it('extracts frontmatter to "customFrontmatter" export when configured', async () => { + const fixture = await loadFixture({ + root: new URL('./fixtures/mdx-custom-frontmatter-name/', import.meta.url), + integrations: [mdx({ + frontmatterOptions: { + name: 'customFrontmatter', + }, + })], + }); + await fixture.build(); + + const { titles } = JSON.parse(await fixture.readFile('/glob.json')); + expect(titles).to.include('Using YAML frontmatter'); + }); +}); |