Skip to content

Commit

Permalink
[MDX] Fix remaining inconsistencies with Markdown (#4268)
Browse files Browse the repository at this point in the history
* feat: add "file" and "url" to layout props

* feat: add rawContent and compiledContent errs

* fix: add "file" and "url" to frontmatter

* fix: add separate MDX instance type

* types: add MarkdownLayoutProps and MDXLayoutProps

* refactor: simplify MDXLayoutProps

* test: pass file and url to layout

* test: glob components with .default and Content

* feat: add <Content /> to MDX

* feat: declare MDX type module

* fix: [MD] move file and url to layout props only

* chore: changeset

* chore: bump MDX to "minor" with more details

* refactor: remove "file" + "url" top-level props (save for minor)

* revert: MDInstance type def updates (save for minor)

* fix: MDXInstance "default" + "content" types

* fix: bad test layout

* chore: remove getHeaders fro *.mdx
  • Loading branch information
bholmesdev committed Aug 12, 2022
1 parent 437bf73 commit f7afdb8
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 6 deletions.
9 changes: 9 additions & 0 deletions .changeset/empty-parents-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'astro': patch
'@astrojs/mdx': minor
---

Align MD with MDX on layout props and "glob" import results:
- Add `Content` to MDX
- Add `file` and `url` to MDX frontmatter (layout import only)
- Update glob types to reflect differences (lack of `rawContent` and `compiledContent`)
15 changes: 15 additions & 0 deletions packages/astro/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ declare module '*.md' {
export default load;
}

declare module '*.mdx' {
type MDX = import('astro').MDXInstance<Record<string, any>>;

export const frontmatter: MDX['frontmatter'];
export const file: MDX['file'];
export const url: MDX['url'];
export const getHeadings: MDX['getHeadings'];
export const Content: MDX['Content'];
export const rawContent: MDX['rawContent'];
export const compiledContent: MDX['compiledContent'];

const load: MDX['default'];
export default load;
}

declare module '*.html' {
const Component: { render(opts: { slots: Record<string, string> }): string };
export default Component;
Expand Down
25 changes: 24 additions & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export interface AstroGlobalPartial {
*/
glob(globStr: `${any}.astro`): Promise<AstroInstance[]>;
glob<T extends Record<string, any>>(globStr: `${any}.md`): Promise<MarkdownInstance<T>[]>;
glob<T extends Record<string, any>>(globStr: `${any}.mdx`): Promise<MarkdownInstance<T>[]>;
glob<T extends Record<string, any>>(globStr: `${any}.mdx`): Promise<MDXInstance<T>[]>;
glob<T extends Record<string, any>>(globStr: string): Promise<T[]>;
/**
* Returns a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) object built from the [site](https://docs.astro.build/en/reference/configuration-reference/#site) config option
Expand Down Expand Up @@ -860,6 +860,29 @@ export interface MarkdownInstance<T extends Record<string, any>> {
}>;
}

export interface MDXInstance<T>
extends Omit<MarkdownInstance<T>, 'rawContent' | 'compiledContent' | 'Content' | 'default'> {
/** 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 */
rawContent: never;
/** 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 */
compiledContent: never;
default: AstroComponentFactory;
Content: AstroComponentFactory;
}

export interface MarkdownLayoutProps<T extends Record<string, any>> {
frontmatter: {
file: MarkdownInstance<T>['file'];
url: MarkdownInstance<T>['url'];
} & T;
headings: MarkdownHeading[];
rawContent: MarkdownInstance<T>['rawContent'];
compiledContent: MarkdownInstance<T>['compiledContent'];
}

export interface MDXLayoutProps<T>
extends Omit<MarkdownLayoutProps<T>, 'rawContent' | 'compiledContent'> {}

export type GetHydrateCallback = () => Promise<() => void | Promise<void>>;

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/vite-plugin-markdown/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
const frontmatter = {
...injectedFrontmatter,
...raw.data,
url: fileUrl,
file: fileId,
} as any;

const { layout } = frontmatter;
Expand Down Expand Up @@ -86,6 +84,8 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
};
export async function Content() {
const { layout, ...content } = frontmatter;
content.file = file;
content.url = url;
content.astro = {};
Object.defineProperty(content.astro, 'headings', {
get() {
Expand Down
2 changes: 2 additions & 0 deletions packages/integrations/mdx/src/astro-data-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any
export default function ({ children }) {
const { layout, ...content } = frontmatter;
content.file = file;
content.url = url;
content.astro = {};
Object.defineProperty(content.astro, 'headings', {
get() {
Expand Down
19 changes: 19 additions & 0 deletions packages/integrations/mdx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const DEFAULT_REMARK_PLUGINS: MdxRollupPluginOptions['remarkPlugins'] = [
];
const DEFAULT_REHYPE_PLUGINS: MdxRollupPluginOptions['rehypePlugins'] = [];

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';

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<T>(config: WithExtends<T[] | undefined>, defaults: T[] = []): T[] {
if (Array.isArray(config)) return config;

Expand Down Expand Up @@ -127,6 +133,19 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
if (!moduleExports.includes('file')) {
code += `\nexport const file = ${JSON.stringify(fileId)};`;
}
if (!moduleExports.includes('rawContent')) {
code += `\nexport function rawContent() { throw new Error(${JSON.stringify(
RAW_CONTENT_ERROR
)}) };`;
}
if (!moduleExports.includes('compiledContent')) {
code += `\nexport function compiledContent() { throw new Error(${JSON.stringify(
COMPILED_CONTENT_ERROR
)}) };`;
}
if (!moduleExports.includes('Content')) {
code += `\nexport const Content = MDXContent;`;
}

if (command === 'dev') {
// TODO: decline HMR updates until we have a stable approach
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
const components = await Astro.glob('../components/*.mdx');
---

<div data-default-export>
{components.map(Component => <Component.default />)}
</div>

<div data-content-export>
{components.map(({ Content }) => <Content />)}
</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
---
const {
content = { title: "content didn't work" },
frontmatter = { title: "frontmatter didn't work" },
frontmatter = {
title: "frontmatter didn't work",
file: "file didn't work",
url: "url didn't work",
},
headings = [],
} = Astro.props;
---
Expand All @@ -18,6 +22,8 @@ const {
<body>
<p data-content-title>{content.title}</p>
<p data-frontmatter-title>{frontmatter.title}</p>
<p data-frontmatter-file>{frontmatter.file}</p>
<p data-frontmatter-url>{frontmatter.url}</p>
<p data-layout-rendered>Layout rendered!</p>
<ul data-headings>
{headings.map(heading => <li>{heading.slug}</li>)}
Expand Down
56 changes: 54 additions & 2 deletions packages/integrations/mdx/test/mdx-component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('MDX Component', () => {
await fixture.build();
});

it('works', async () => {
it('supports top-level imports', async () => {
const html = await fixture.readFile('/index.html');
const { document } = parseHTML(html);

Expand All @@ -29,6 +29,28 @@ describe('MDX Component', () => {
expect(h1.textContent).to.equal('Hello component!');
expect(foo.textContent).to.equal('bar');
});

it('supports glob imports - <Component.default />', async () => {
const html = await fixture.readFile('/glob/index.html');
const { document } = parseHTML(html);

const h1 = document.querySelector('[data-default-export] h1');
const foo = document.querySelector('[data-default-export] #foo');

expect(h1.textContent).to.equal('Hello component!');
expect(foo.textContent).to.equal('bar');
});

it('supports glob imports - <Content />', async () => {
const html = await fixture.readFile('/glob/index.html');
const { document } = parseHTML(html);

const h1 = document.querySelector('[data-content-export] h1');
const foo = document.querySelector('[data-content-export] #foo');

expect(h1.textContent).to.equal('Hello component!');
expect(foo.textContent).to.equal('bar');
});
});

describe('dev', () => {
Expand All @@ -42,7 +64,7 @@ describe('MDX Component', () => {
await devServer.stop();
});

it('works', async () => {
it('supports top-level imports', async () => {
const res = await fixture.fetch('/');

expect(res.status).to.equal(200);
Expand All @@ -56,5 +78,35 @@ describe('MDX Component', () => {
expect(h1.textContent).to.equal('Hello component!');
expect(foo.textContent).to.equal('bar');
});

it('supports glob imports - <Component.default />', async () => {
const res = await fixture.fetch('/glob');

expect(res.status).to.equal(200);

const html = await res.text();
const { document } = parseHTML(html);

const h1 = document.querySelector('[data-default-export] h1');
const foo = document.querySelector('[data-default-export] #foo');

expect(h1.textContent).to.equal('Hello component!');
expect(foo.textContent).to.equal('bar');
});

it('supports glob imports - <Content />', async () => {
const res = await fixture.fetch('/glob');

expect(res.status).to.equal(200);

const html = await res.text();
const { document } = parseHTML(html);

const h1 = document.querySelector('[data-content-export] h1');
const foo = document.querySelector('[data-content-export] #foo');

expect(h1.textContent).to.equal('Hello component!');
expect(foo.textContent).to.equal('bar');
});
});
});
11 changes: 11 additions & 0 deletions packages/integrations/mdx/test/mdx-frontmatter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,15 @@ describe('MDX frontmatter', () => {
expect(headingSlugs).to.contain('section-1');
expect(headingSlugs).to.contain('section-2');
});

it('passes "file" and "url" to layout via frontmatter', async () => {
const html = await fixture.readFile('/with-headings/index.html');
const { document } = parseHTML(html);

const frontmatterFile = document.querySelector('[data-frontmatter-file]')?.textContent;
const frontmatterUrl = document.querySelector('[data-frontmatter-url]')?.textContent;

expect(frontmatterFile?.endsWith('with-headings.mdx')).to.equal(true, '"file" prop does not end with correct path or is undefined');
expect(frontmatterUrl).to.equal('/with-headings');
});
});

0 comments on commit f7afdb8

Please sign in to comment.