diff --git a/beta/src/pages/[[...markdownPath]].js b/beta/src/pages/[[...markdownPath]].js index 07eff0dd..1c8ad6fe 100644 --- a/beta/src/pages/[[...markdownPath]].js +++ b/beta/src/pages/[[...markdownPath]].js @@ -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 ( - - {children} + + {parsedContent} ); @@ -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, }, }; diff --git a/beta/src/utils/prepareMDX.js b/beta/src/utils/prepareMDX.js index efe64c9b..296b66af 100644 --- a/beta/src/utils/prepareMDX.js +++ b/beta/src/utils/prepareMDX.js @@ -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({wrapQueue}); + const Wrapper = 'MaxWidth'; + finalChildren.push({wrapQueue}); 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, }; });