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 YAML frontmatter #3995

Merged
merged 11 commits into from
Jul 21, 2022
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function shouldSkipDraft(pageModule: ComponentInstance, astroConfig: AstroConfig
!astroConfig.markdown.drafts &&
// This is a draft post
'frontmatter' in pageModule &&
(pageModule as any).frontmatter.draft === true
(pageModule as any).frontmatter?.draft === true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remark-mdx-frontmatter applies export const frontmatter = undefined when no frontmatter is present. This handles that case!

);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/integrations/mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
28 changes: 23 additions & 5 deletions packages/integrations/mdx/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
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 };

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 ?? [])];
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: 'Using YAML frontmatter'
illThrowIfIDontExist: "Oh no, that's scary!"
---

# {customFrontmatter.illThrowIfIDontExist}
Original file line number Diff line number Diff line change
@@ -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),
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: 'Using YAML frontmatter'
illThrowIfIDontExist: "Oh no, that's scary!"
---

# {frontmatter.illThrowIfIDontExist}
43 changes: 43 additions & 0 deletions packages/integrations/mdx/test/mdx-frontmatter.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
Loading