From 2fb72c887f71c0a69ab512870d65b8c867774766 Mon Sep 17 00:00:00 2001 From: Ben Holmes Date: Wed, 1 Feb 2023 08:33:18 -0500 Subject: [PATCH] [Content collections] Apply MDX components on render (#6064) * fix: apply MDX components during render() * test: MDX components export in SSG and SSR * chore: changeset --- .changeset/heavy-tomatoes-know.md | 5 +++ packages/astro/src/content/internal.ts | 39 ++++++++++++++----- .../src/content/vite-plugin-content-assets.ts | 4 +- .../test/content-collections-render.test.js | 33 ++++++++++++++++ .../fixtures/content/src/components/H2.astro | 4 ++ .../promo/launch-week-components-export.mdx | 18 +++++++++ .../pages/launch-week-components-export.astro | 14 +++++++ 7 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 .changeset/heavy-tomatoes-know.md create mode 100644 packages/astro/test/fixtures/content/src/components/H2.astro create mode 100644 packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-components-export.mdx create mode 100644 packages/astro/test/fixtures/content/src/pages/launch-week-components-export.astro diff --git a/.changeset/heavy-tomatoes-know.md b/.changeset/heavy-tomatoes-know.md new file mode 100644 index 000000000000..ae8fd2daccc5 --- /dev/null +++ b/.changeset/heavy-tomatoes-know.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Apply MDX `components` export when rendering as a content collection entry diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index 2b8f8ba6fe70..04280166feb3 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -1,3 +1,4 @@ +import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { prependForwardSlash } from '../core/path.js'; import { @@ -120,21 +121,32 @@ async function render({ id: string; collectionToRenderEntryMap: CollectionToEntryMap; }) { + const UnexpectedRenderError = new AstroError({ + ...AstroErrorData.UnknownContentCollectionError, + message: `Unexpected error while rendering ${String(collection)} → ${String(id)}.`, + }); + const lazyImport = collectionToRenderEntryMap[collection]?.[id]; - if (!lazyImport) throw new Error(`${String(collection)} → ${String(id)} does not exist.`); + if (typeof lazyImport !== 'function') throw UnexpectedRenderError; + + const baseMod = await lazyImport(); + if (baseMod == null || typeof baseMod !== 'object') throw UnexpectedRenderError; - const mod = await lazyImport(); + const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod; + if (typeof getMod !== 'function') throw UnexpectedRenderError; + const mod = await getMod(); + if (mod == null || typeof mod !== 'object') throw UnexpectedRenderError; const Content = createComponent({ - factory(result, props, slots) { + factory(result, baseProps, slots) { let styles = '', links = '', scripts = ''; - if (Array.isArray(mod?.collectedStyles)) { - styles = mod.collectedStyles.map((style: any) => renderStyleElement(style)).join(''); + if (Array.isArray(collectedStyles)) { + styles = collectedStyles.map((style: any) => renderStyleElement(style)).join(''); } - if (Array.isArray(mod?.collectedLinks)) { - links = mod.collectedLinks + if (Array.isArray(collectedLinks)) { + links = collectedLinks .map((link: any) => { return renderUniqueStylesheet(result, { href: prependForwardSlash(link), @@ -142,8 +154,17 @@ async function render({ }) .join(''); } - if (Array.isArray(mod?.collectedScripts)) { - scripts = mod.collectedScripts.map((script: any) => renderScriptElement(script)).join(''); + if (Array.isArray(collectedScripts)) { + scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join(''); + } + + let props = baseProps; + // Auto-apply MDX components export + if (id.endsWith('mdx')) { + props = { + components: mod.components ?? {}, + ...baseProps, + }; } return createHeadAndContent( diff --git a/packages/astro/src/content/vite-plugin-content-assets.ts b/packages/astro/src/content/vite-plugin-content-assets.ts index 54b84380d2d9..fd73caf4750d 100644 --- a/packages/astro/src/content/vite-plugin-content-assets.ts +++ b/packages/astro/src/content/vite-plugin-content-assets.ts @@ -36,7 +36,9 @@ export function astroContentAssetPropagationPlugin({ mode }: { mode: string }): if (isPropagatedAsset(id)) { const basePath = id.split('?')[0]; const code = ` - export { Content, getHeadings, frontmatter } from ${JSON.stringify(basePath)}; + export async function getMod() { + return import(${JSON.stringify(basePath)}); + } export const collectedLinks = ${JSON.stringify(LINKS_PLACEHOLDER)}; export const collectedStyles = ${JSON.stringify(STYLES_PLACEHOLDER)}; export const collectedScripts = ${JSON.stringify(SCRIPTS_PLACEHOLDER)}; diff --git a/packages/astro/test/content-collections-render.test.js b/packages/astro/test/content-collections-render.test.js index 92015393c1d8..da14a4765f05 100644 --- a/packages/astro/test/content-collections-render.test.js +++ b/packages/astro/test/content-collections-render.test.js @@ -71,6 +71,15 @@ describe('Content Collections - render()', () => { '`WithScripts.astro` hoisted script included unexpectedly.' ).to.be.undefined; }); + + it('Applies MDX components export', async () => { + const html = await fixture.readFile('/launch-week-components-export/index.html'); + const $ = cheerio.load(html); + + const h2 = $('h2'); + expect(h2).to.have.a.lengthOf(1); + expect(h2.attr('data-components-export-applied')).to.equal('true'); + }); }); describe('Build - SSR', () => { @@ -110,6 +119,18 @@ describe('Content Collections - render()', () => { // Includes styles expect($('link[rel=stylesheet]')).to.have.a.lengthOf(0); }); + + it('Applies MDX components export', async () => { + const app = await fixture.loadTestAdapterApp(); + const request = new Request('http://example.com/launch-week-components-export'); + const response = await app.render(request); + const html = await response.text(); + const $ = cheerio.load(html); + + const h2 = $('h2'); + expect(h2).to.have.a.lengthOf(1); + expect(h2.attr('data-components-export-applied')).to.equal('true'); + }); }); describe('Dev - SSG', () => { @@ -162,5 +183,17 @@ describe('Content Collections - render()', () => { // Includes inline script expect($('script[data-is-inline]')).to.have.a.lengthOf(1); }); + + it('Applies MDX components export', async () => { + const response = await fixture.fetch('/launch-week-components-export', { method: 'GET' }); + expect(response.status).to.equal(200); + + const html = await response.text(); + const $ = cheerio.load(html); + + const h2 = $('h2'); + expect(h2).to.have.a.lengthOf(1); + expect(h2.attr('data-components-export-applied')).to.equal('true'); + }); }); }); diff --git a/packages/astro/test/fixtures/content/src/components/H2.astro b/packages/astro/test/fixtures/content/src/components/H2.astro new file mode 100644 index 000000000000..d1ad359c2ee1 --- /dev/null +++ b/packages/astro/test/fixtures/content/src/components/H2.astro @@ -0,0 +1,4 @@ +--- +--- + +

diff --git a/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-components-export.mdx b/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-components-export.mdx new file mode 100644 index 000000000000..40012b8ef46b --- /dev/null +++ b/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-components-export.mdx @@ -0,0 +1,18 @@ +--- +title: 'Launch week!' +description: 'Join us for the exciting launch of SPACE BLOG' +publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' +tags: ['announcement'] +--- + +import H2 from '../../../components/H2.astro'; + +export const components = { h2: H2 }; + +Join us for the space blog launch! + +## Details + +- THIS THURSDAY +- Houston, TX +- Dress code: **interstellar casual** ✨ diff --git a/packages/astro/test/fixtures/content/src/pages/launch-week-components-export.astro b/packages/astro/test/fixtures/content/src/pages/launch-week-components-export.astro new file mode 100644 index 000000000000..463e6519129c --- /dev/null +++ b/packages/astro/test/fixtures/content/src/pages/launch-week-components-export.astro @@ -0,0 +1,14 @@ +--- +import { getEntryBySlug } from 'astro:content'; + +const entry = await getEntryBySlug('blog', 'promo/launch-week-components-export'); +const { Content } = await entry.render(); +--- + + + Launch Week + + + + +