diff --git a/.changeset/empty-parents-lie.md b/.changeset/empty-parents-lie.md new file mode 100644 index 000000000000..de4a2e015ad2 --- /dev/null +++ b/.changeset/empty-parents-lie.md @@ -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`) diff --git a/packages/astro/env.d.ts b/packages/astro/env.d.ts index ff53148989b9..4b6ce7750318 100644 --- a/packages/astro/env.d.ts +++ b/packages/astro/env.d.ts @@ -29,6 +29,21 @@ declare module '*.md' { export default load; } +declare module '*.mdx' { + type MDX = import('astro').MDXInstance>; + + 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 }; export default Component; diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index b45b01b5bc67..5c8473e58123 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -241,7 +241,7 @@ export interface AstroGlobalPartial { */ glob(globStr: `${any}.astro`): Promise; glob>(globStr: `${any}.md`): Promise[]>; - glob>(globStr: `${any}.mdx`): Promise[]>; + glob>(globStr: `${any}.mdx`): Promise[]>; glob>(globStr: string): Promise; /** * 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 @@ -860,6 +860,29 @@ export interface MarkdownInstance> { }>; } +export interface MDXInstance + extends Omit, '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> { + frontmatter: { + file: MarkdownInstance['file']; + url: MarkdownInstance['url']; + } & T; + headings: MarkdownHeading[]; + rawContent: MarkdownInstance['rawContent']; + compiledContent: MarkdownInstance['compiledContent']; +} + +export interface MDXLayoutProps + extends Omit, 'rawContent' | 'compiledContent'> {} + export type GetHydrateCallback = () => Promise<() => void | Promise>; /** diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index d025d669da76..53b584f4a94e 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -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; @@ -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() { diff --git a/packages/integrations/mdx/src/astro-data-utils.ts b/packages/integrations/mdx/src/astro-data-utils.ts index 0bc375c27fa6..f3d04e431617 100644 --- a/packages/integrations/mdx/src/astro-data-utils.ts +++ b/packages/integrations/mdx/src/astro-data-utils.ts @@ -28,6 +28,8 @@ export function rehypeApplyFrontmatterExport(pageFrontmatter: Record(config: WithExtends, defaults: T[] = []): T[] { if (Array.isArray(config)) return config; @@ -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 diff --git a/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/glob.astro b/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/glob.astro new file mode 100644 index 000000000000..ae857fe27f90 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-component/src/pages/glob.astro @@ -0,0 +1,11 @@ +--- +const components = await Astro.glob('../components/*.mdx'); +--- + +
+ {components.map(Component => )} +
+ +
+ {components.map(({ Content }) => )} +
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/layouts/Base.astro b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/layouts/Base.astro index e4fa7560acf9..9a1c84c9d042 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/layouts/Base.astro +++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/layouts/Base.astro @@ -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; --- @@ -18,6 +22,8 @@ const {

{content.title}

{frontmatter.title}

+

{frontmatter.file}

+

{frontmatter.url}

Layout rendered!

    {headings.map(heading =>
  • {heading.slug}
  • )} diff --git a/packages/integrations/mdx/test/mdx-component.test.js b/packages/integrations/mdx/test/mdx-component.test.js index 1a610be05985..84210b3420e6 100644 --- a/packages/integrations/mdx/test/mdx-component.test.js +++ b/packages/integrations/mdx/test/mdx-component.test.js @@ -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); @@ -29,6 +29,28 @@ describe('MDX Component', () => { expect(h1.textContent).to.equal('Hello component!'); expect(foo.textContent).to.equal('bar'); }); + + it('supports glob imports - ', 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 - ', 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', () => { @@ -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); @@ -56,5 +78,35 @@ describe('MDX Component', () => { expect(h1.textContent).to.equal('Hello component!'); expect(foo.textContent).to.equal('bar'); }); + + it('supports glob imports - ', 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 - ', 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'); + }); }); }); diff --git a/packages/integrations/mdx/test/mdx-frontmatter.test.js b/packages/integrations/mdx/test/mdx-frontmatter.test.js index e69919bd51b9..9f0e9d8e62c4 100644 --- a/packages/integrations/mdx/test/mdx-frontmatter.test.js +++ b/packages/integrations/mdx/test/mdx-frontmatter.test.js @@ -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'); + }); });