[Content collections] Apply MDX components on render (#6064)

* fix: apply MDX components during render()

* test: MDX components export in SSG and SSR

* chore: changeset
This commit is contained in:
Ben Holmes 2023-02-01 08:33:18 -05:00 committed by Matthew Phillips
parent a784e603a9
commit 80dc1a5cbd
7 changed files with 108 additions and 11 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Apply MDX `components` export when rendering as a content collection entry

View file

@ -1,3 +1,4 @@
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { prependForwardSlash } from '../core/path.js'; import { prependForwardSlash } from '../core/path.js';
import { import {
@ -120,21 +121,32 @@ async function render({
id: string; id: string;
collectionToRenderEntryMap: CollectionToEntryMap; collectionToRenderEntryMap: CollectionToEntryMap;
}) { }) {
const lazyImport = collectionToRenderEntryMap[collection]?.[id]; const UnexpectedRenderError = new AstroError({
if (!lazyImport) throw new Error(`${String(collection)}${String(id)} does not exist.`); ...AstroErrorData.UnknownContentCollectionError,
message: `Unexpected error while rendering ${String(collection)}${String(id)}.`,
});
const mod = await lazyImport(); const lazyImport = collectionToRenderEntryMap[collection]?.[id];
if (typeof lazyImport !== 'function') throw UnexpectedRenderError;
const baseMod = await lazyImport();
if (baseMod == null || typeof baseMod !== 'object') throw UnexpectedRenderError;
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({ const Content = createComponent({
factory(result, props, slots) { factory(result, baseProps, slots) {
let styles = '', let styles = '',
links = '', links = '',
scripts = ''; scripts = '';
if (Array.isArray(mod?.collectedStyles)) { if (Array.isArray(collectedStyles)) {
styles = mod.collectedStyles.map((style: any) => renderStyleElement(style)).join(''); styles = collectedStyles.map((style: any) => renderStyleElement(style)).join('');
} }
if (Array.isArray(mod?.collectedLinks)) { if (Array.isArray(collectedLinks)) {
links = mod.collectedLinks links = collectedLinks
.map((link: any) => { .map((link: any) => {
return renderUniqueStylesheet(result, { return renderUniqueStylesheet(result, {
href: prependForwardSlash(link), href: prependForwardSlash(link),
@ -142,8 +154,17 @@ async function render({
}) })
.join(''); .join('');
} }
if (Array.isArray(mod?.collectedScripts)) { if (Array.isArray(collectedScripts)) {
scripts = mod.collectedScripts.map((script: any) => renderScriptElement(script)).join(''); 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( return createHeadAndContent(

View file

@ -36,7 +36,9 @@ export function astroContentAssetPropagationPlugin({ mode }: { mode: string }):
if (isPropagatedAsset(id)) { if (isPropagatedAsset(id)) {
const basePath = id.split('?')[0]; const basePath = id.split('?')[0];
const code = ` 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 collectedLinks = ${JSON.stringify(LINKS_PLACEHOLDER)};
export const collectedStyles = ${JSON.stringify(STYLES_PLACEHOLDER)}; export const collectedStyles = ${JSON.stringify(STYLES_PLACEHOLDER)};
export const collectedScripts = ${JSON.stringify(SCRIPTS_PLACEHOLDER)}; export const collectedScripts = ${JSON.stringify(SCRIPTS_PLACEHOLDER)};

View file

@ -69,6 +69,15 @@ describe('Content Collections - render()', () => {
'`WithScripts.astro` hoisted script included unexpectedly.' '`WithScripts.astro` hoisted script included unexpectedly.'
).to.be.undefined; ).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', () => { describe('Build - SSR', () => {
@ -108,6 +117,18 @@ describe('Content Collections - render()', () => {
// Includes styles // Includes styles
expect($('link[rel=stylesheet]')).to.have.a.lengthOf(0); 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', () => { describe('Dev - SSG', () => {
@ -160,5 +181,17 @@ describe('Content Collections - render()', () => {
// Includes inline script // Includes inline script
expect($('script[data-is-inline]')).to.have.a.lengthOf(1); 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');
});
}); });
}); });

View file

@ -0,0 +1,4 @@
---
---
<h2 data-components-export-applied="true"><slot /></h2>

View file

@ -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** ✨

View file

@ -0,0 +1,14 @@
---
import { getEntryBySlug } from 'astro:content';
const entry = await getEntryBySlug('blog', 'promo/launch-week-components-export');
const { Content } = await entry.render();
---
<html>
<head>
<title>Launch Week</title>
</head>
<body>
<Content />
</body>
</html>