diff --git a/packages/docusaurus-mdx-loader/src/__tests__/processor.test.ts b/packages/docusaurus-mdx-loader/src/__tests__/processor.test.ts index 46286ad41502..abc6eef7ea96 100644 --- a/packages/docusaurus-mdx-loader/src/__tests__/processor.test.ts +++ b/packages/docusaurus-mdx-loader/src/__tests__/processor.test.ts @@ -73,6 +73,16 @@ const TestMDXComponents = { React.createElement('header', null, title ?? 'Default title'), children, ), + // Like react-helmet-async, Head only handles its element children + // Rendered separately, otherwise React hoists and <meta> out of it + Head: ({children}: {children?: ReactNode}) => + React.createElement('test-head', { + dangerouslySetInnerHTML: { + __html: renderToStaticMarkup( + React.Children.toArray(children).filter(React.isValidElement), + ), + }, + }), }; async function renderContent(content: string, options: Options) { @@ -265,6 +275,41 @@ Body }); }); + describe('head', () => { + describe.each(['md', 'mdx'] as const)('%s format', (format) => { + it('renders head metadata', async () => { + const html = await renderContent( + `<head> + <title>Custom title + + + +Body`, + createOptions({markdownConfig: {format}}), + ); + expect(html).toBe( + 'Custom title\n

Body

', + ); + }); + }); + + it('renders structured data with the md format', async () => { + const html = await renderContent( + ` + +`, + createOptions({markdownConfig: {format: 'md'}}), + ); + expect(html).toBe( + ``, + ); + }); + }); + it.each([true, false])('supports emoji=%s', async (emoji) => { const options = createOptions({markdownConfig: {emoji}}); const result = await processContent( diff --git a/packages/docusaurus-mdx-loader/src/remark/head/index.ts b/packages/docusaurus-mdx-loader/src/remark/head/index.ts index 4ec2b49756e8..2ca737e1cf9c 100644 --- a/packages/docusaurus-mdx-loader/src/remark/head/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/head/index.ts @@ -6,18 +6,53 @@ */ import {visit} from 'unist-util-visit'; +import {transformNode} from '../utils'; import type {Transformer} from 'unified'; import type {Root} from 'mdast'; +import type {MdxJsxFlowElement} from 'mdast-util-mdx'; // Transform to // MDX 2 doesn't allow to substitute html elements with the provider anymore export default function plugin(): Transformer { return (root) => { - visit(root, 'mdxJsxFlowElement', (node) => { - if (node.name === 'head') { - node.name = 'Head'; - } - }); + transformJsxHead(root); + transformHtmlHead(root); }; } + +// With the MDX format, is a JSX element +function transformJsxHead(root: Root) { + visit(root, 'mdxJsxFlowElement', (node) => { + if (node.name === 'head') { + node.name = 'Head'; + } + }); +} + +// With the CommonMark format, is a raw HTML block +// rehype-raw parses raw HTML in body context, where the tag is dropped +// We wrap its content in a JSX element, that rehype-raw passes through +// The content remains raw HTML that rehype-raw parses inside +function transformHtmlHead(root: Root) { + visit(root, 'html', (node) => { + const headContent = getHeadContent(node.value); + if (headContent === undefined) { + return; + } + const headElement: MdxJsxFlowElement = { + type: 'mdxJsxFlowElement', + name: 'Head', + attributes: [], + children: [{type: 'html', value: headContent}], + position: node.position, + }; + transformNode(node, headElement); + }); +} + +const HeadHtmlRegExp = /^(?.*)<\/head>$/s; + +function getHeadContent(html: string): string | undefined { + return HeadHtmlRegExp.exec(html.trim())?.groups?.content; +} diff --git a/website/_dogfooding/_pages tests/markdown-tests-md.md b/website/_dogfooding/_pages tests/markdown-tests-md.md index 1f09421176ce..ba7df4fa158b 100644 --- a/website/_dogfooding/_pages tests/markdown-tests-md.md +++ b/website/_dogfooding/_pages tests/markdown-tests-md.md @@ -14,19 +14,21 @@ This file should be interpreted in a more CommonMark compliant way HEAD Markdown Page tests title + ``` HEAD Markdown Page tests title + -:::danger - -TODO unsupported (yet), see [issue](https://github.com/meta/docusaurus/issues/9092) - -::: +The title, keywords and structured data should be in the `` of the static HTML page ## Comment @@ -154,12 +156,6 @@ function Clock(props) { } ``` -:::danger - -TODO unsupported (yet), see [issue](https://github.com/meta/docusaurus/issues/9092) - -::: - ## Mermaid ```mermaid