mirror of
https://github.com/reactjs/react.dev.git
synced 2026-02-25 13:13:08 +00:00
[Beta] Pre-process MDX during build (#4994)
This commit is contained in:
@@ -2,25 +2,21 @@
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*/
|
||||
|
||||
import {createElement, Children, Fragment, useMemo} from 'react';
|
||||
import {Fragment, useMemo} from 'react';
|
||||
import {MDXComponents} from 'components/MDX/MDXComponents';
|
||||
import {MarkdownPage} from 'components/Layout/MarkdownPage';
|
||||
import {Page} from 'components/Layout/Page';
|
||||
import {prepareMDX} from '../utils/prepareMDX';
|
||||
|
||||
export default function Layout({content, meta}) {
|
||||
const decoded = useMemo(
|
||||
export default function Layout({content, toc, meta}) {
|
||||
const parsedContent = useMemo(
|
||||
() => JSON.parse(content, reviveNodeOnClient),
|
||||
[content]
|
||||
);
|
||||
const {toc, children} = useMemo(
|
||||
() => prepareMDX(decoded.props.children),
|
||||
[decoded]
|
||||
);
|
||||
const parsedToc = useMemo(() => JSON.parse(toc, reviveNodeOnClient), [toc]);
|
||||
return (
|
||||
<Page>
|
||||
<MarkdownPage meta={meta} toc={toc}>
|
||||
{children}
|
||||
<MarkdownPage meta={meta} toc={parsedToc}>
|
||||
{parsedContent}
|
||||
</MarkdownPage>
|
||||
</Page>
|
||||
);
|
||||
@@ -82,7 +78,7 @@ export async function getStaticProps(context) {
|
||||
const {remarkPlugins} = require('../../plugins/markdownToHtml');
|
||||
const rootDir = process.cwd() + '/src/content/';
|
||||
|
||||
// Read MDX and make JS out of it
|
||||
// Read MDX from the file. Parse Frontmatter data out of it.
|
||||
let path = (context.params.markdownPath || []).join('/') || 'index';
|
||||
let mdxWithFrontmatter;
|
||||
try {
|
||||
@@ -91,22 +87,43 @@ export async function getStaticProps(context) {
|
||||
mdxWithFrontmatter = fs.readFileSync(rootDir + path + '/index.md', 'utf8');
|
||||
}
|
||||
const {content: mdx, data: meta} = fm(mdxWithFrontmatter);
|
||||
const jsx = await compileMdx(mdx, {
|
||||
|
||||
// Turn the MDX we just read into some JS we can execute.
|
||||
let mdxWithFakeImports = '';
|
||||
for (let key in MDXComponents) {
|
||||
if (MDXComponents.hasOwnProperty(key)) {
|
||||
// If we don't add these fake imports, the MDX compiler
|
||||
// will insert a bunch of opaque components we can't introspect.
|
||||
// This will break the prepareMDX() call below.
|
||||
mdxWithFakeImports += 'import ' + key + ' from "' + key + '";\n';
|
||||
}
|
||||
}
|
||||
mdxWithFakeImports += '\n' + mdx;
|
||||
const jsxCode = await compileMdx(mdxWithFakeImports, {
|
||||
remarkPlugins,
|
||||
});
|
||||
const js = transform(jsx, {
|
||||
const jsCode = transform(jsxCode, {
|
||||
plugins: ['@babel/plugin-transform-modules-commonjs'],
|
||||
presets: ['@babel/preset-react'],
|
||||
}).code;
|
||||
|
||||
// Run it to get JSON for render output
|
||||
const run = new Function('exports', 'mdx', js);
|
||||
let outputExports = {};
|
||||
run(outputExports, createElement);
|
||||
const reactTree = outputExports.default({});
|
||||
// Prepare environment for MDX and then eval it.
|
||||
let fakeExports = {};
|
||||
// For each fake MDX import, give back the string component name.
|
||||
// It will get serialized later.
|
||||
const fakeRequire = (key) => key;
|
||||
const evalJSCode = new Function('require', 'exports', 'mdx', jsCode);
|
||||
const createElement = require('react').createElement;
|
||||
evalJSCode(fakeRequire, fakeExports, createElement);
|
||||
const reactTree = fakeExports.default({});
|
||||
|
||||
// Pre-process MDX output and serialize it.
|
||||
const {prepareMDX} = require('../utils/prepareMDX');
|
||||
const {toc, children} = prepareMDX(reactTree.props.children);
|
||||
return {
|
||||
props: {
|
||||
content: JSON.stringify(reactTree, stringifyNodeOnServer),
|
||||
content: JSON.stringify(children, stringifyNodeOnServer),
|
||||
toc: JSON.stringify(toc, stringifyNodeOnServer),
|
||||
meta,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import {MDXComponents} from 'components/MDX/MDXComponents';
|
||||
|
||||
const {MaxWidth} = MDXComponents;
|
||||
|
||||
// TODO: This logic should be in MDX plugins instead.
|
||||
// TODO: This logic could be in MDX plugins instead.
|
||||
|
||||
export function prepareMDX(rawChildren) {
|
||||
const toc = getTableOfContents(rawChildren);
|
||||
@@ -32,7 +32,8 @@ function wrapChildrenInMaxWidthContainers(children) {
|
||||
let finalChildren = [];
|
||||
function flushWrapper(key) {
|
||||
if (wrapQueue.length > 0) {
|
||||
finalChildren.push(<MaxWidth key={key}>{wrapQueue}</MaxWidth>);
|
||||
const Wrapper = 'MaxWidth';
|
||||
finalChildren.push(<Wrapper key={key}>{wrapQueue}</Wrapper>);
|
||||
wrapQueue = [];
|
||||
}
|
||||
}
|
||||
@@ -44,7 +45,7 @@ function wrapChildrenInMaxWidthContainers(children) {
|
||||
wrapQueue.push(child);
|
||||
return;
|
||||
}
|
||||
if (fullWidthTypes.includes(child.type.mdxName)) {
|
||||
if (fullWidthTypes.includes(child.type)) {
|
||||
flushWrapper(key);
|
||||
finalChildren.push(child);
|
||||
} else {
|
||||
@@ -59,22 +60,20 @@ function wrapChildrenInMaxWidthContainers(children) {
|
||||
function getTableOfContents(children) {
|
||||
const anchors = Children.toArray(children)
|
||||
.filter((child) => {
|
||||
if (child.type?.mdxName) {
|
||||
return ['h1', 'h2', 'h3', 'Challenges', 'Recap'].includes(
|
||||
child.type.mdxName
|
||||
);
|
||||
if (child.type) {
|
||||
return ['h1', 'h2', 'h3', 'Challenges', 'Recap'].includes(child.type);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map((child) => {
|
||||
if (child.type.mdxName === 'Challenges') {
|
||||
if (child.type === 'Challenges') {
|
||||
return {
|
||||
url: '#challenges',
|
||||
depth: 0,
|
||||
text: 'Challenges',
|
||||
};
|
||||
}
|
||||
if (child.type.mdxName === 'Recap') {
|
||||
if (child.type === 'Recap') {
|
||||
return {
|
||||
url: '#recap',
|
||||
depth: 0,
|
||||
@@ -83,10 +82,7 @@ function getTableOfContents(children) {
|
||||
}
|
||||
return {
|
||||
url: '#' + child.props.id,
|
||||
depth:
|
||||
(child.type?.mdxName &&
|
||||
parseInt(child.type.mdxName.replace('h', ''), 0)) ??
|
||||
0,
|
||||
depth: (child.type && parseInt(child.type.replace('h', ''), 0)) ?? 0,
|
||||
text: child.props.children,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user