Browse Source

[Beta] Pre-process MDX during build (#4994)

main
dan 2 years ago
committed by GitHub
parent
commit
4b993722ac
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 55
      beta/src/pages/[[...markdownPath]].js
  2. 22
      beta/src/utils/prepareMDX.js

55
beta/src/pages/[[...markdownPath]].js

@ -2,25 +2,21 @@
* Copyright (c) Facebook, Inc. and its affiliates. * 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 {MDXComponents} from 'components/MDX/MDXComponents';
import {MarkdownPage} from 'components/Layout/MarkdownPage'; import {MarkdownPage} from 'components/Layout/MarkdownPage';
import {Page} from 'components/Layout/Page'; import {Page} from 'components/Layout/Page';
import {prepareMDX} from '../utils/prepareMDX';
export default function Layout({content, meta}) { export default function Layout({content, toc, meta}) {
const decoded = useMemo( const parsedContent = useMemo(
() => JSON.parse(content, reviveNodeOnClient), () => JSON.parse(content, reviveNodeOnClient),
[content] [content]
); );
const {toc, children} = useMemo( const parsedToc = useMemo(() => JSON.parse(toc, reviveNodeOnClient), [toc]);
() => prepareMDX(decoded.props.children),
[decoded]
);
return ( return (
<Page> <Page>
<MarkdownPage meta={meta} toc={toc}> <MarkdownPage meta={meta} toc={parsedToc}>
{children} {parsedContent}
</MarkdownPage> </MarkdownPage>
</Page> </Page>
); );
@ -82,7 +78,7 @@ export async function getStaticProps(context) {
const {remarkPlugins} = require('../../plugins/markdownToHtml'); const {remarkPlugins} = require('../../plugins/markdownToHtml');
const rootDir = process.cwd() + '/src/content/'; 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 path = (context.params.markdownPath || []).join('/') || 'index';
let mdxWithFrontmatter; let mdxWithFrontmatter;
try { try {
@ -91,22 +87,43 @@ export async function getStaticProps(context) {
mdxWithFrontmatter = fs.readFileSync(rootDir + path + '/index.md', 'utf8'); mdxWithFrontmatter = fs.readFileSync(rootDir + path + '/index.md', 'utf8');
} }
const {content: mdx, data: meta} = fm(mdxWithFrontmatter); 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, remarkPlugins,
}); });
const js = transform(jsx, { const jsCode = transform(jsxCode, {
plugins: ['@babel/plugin-transform-modules-commonjs'], plugins: ['@babel/plugin-transform-modules-commonjs'],
presets: ['@babel/preset-react'], presets: ['@babel/preset-react'],
}).code; }).code;
// Run it to get JSON for render output // Prepare environment for MDX and then eval it.
const run = new Function('exports', 'mdx', js); let fakeExports = {};
let outputExports = {}; // For each fake MDX import, give back the string component name.
run(outputExports, createElement); // It will get serialized later.
const reactTree = outputExports.default({}); 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 { return {
props: { props: {
content: JSON.stringify(reactTree, stringifyNodeOnServer), content: JSON.stringify(children, stringifyNodeOnServer),
toc: JSON.stringify(toc, stringifyNodeOnServer),
meta, meta,
}, },
}; };

22
beta/src/utils/prepareMDX.js

@ -7,7 +7,7 @@ import {MDXComponents} from 'components/MDX/MDXComponents';
const {MaxWidth} = 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) { export function prepareMDX(rawChildren) {
const toc = getTableOfContents(rawChildren); const toc = getTableOfContents(rawChildren);
@ -32,7 +32,8 @@ function wrapChildrenInMaxWidthContainers(children) {
let finalChildren = []; let finalChildren = [];
function flushWrapper(key) { function flushWrapper(key) {
if (wrapQueue.length > 0) { if (wrapQueue.length > 0) {
finalChildren.push(<MaxWidth key={key}>{wrapQueue}</MaxWidth>); const Wrapper = 'MaxWidth';
finalChildren.push(<Wrapper key={key}>{wrapQueue}</Wrapper>);
wrapQueue = []; wrapQueue = [];
} }
} }
@ -44,7 +45,7 @@ function wrapChildrenInMaxWidthContainers(children) {
wrapQueue.push(child); wrapQueue.push(child);
return; return;
} }
if (fullWidthTypes.includes(child.type.mdxName)) { if (fullWidthTypes.includes(child.type)) {
flushWrapper(key); flushWrapper(key);
finalChildren.push(child); finalChildren.push(child);
} else { } else {
@ -59,22 +60,20 @@ function wrapChildrenInMaxWidthContainers(children) {
function getTableOfContents(children) { function getTableOfContents(children) {
const anchors = Children.toArray(children) const anchors = Children.toArray(children)
.filter((child) => { .filter((child) => {
if (child.type?.mdxName) { if (child.type) {
return ['h1', 'h2', 'h3', 'Challenges', 'Recap'].includes( return ['h1', 'h2', 'h3', 'Challenges', 'Recap'].includes(child.type);
child.type.mdxName
);
} }
return false; return false;
}) })
.map((child) => { .map((child) => {
if (child.type.mdxName === 'Challenges') { if (child.type === 'Challenges') {
return { return {
url: '#challenges', url: '#challenges',
depth: 0, depth: 0,
text: 'Challenges', text: 'Challenges',
}; };
} }
if (child.type.mdxName === 'Recap') { if (child.type === 'Recap') {
return { return {
url: '#recap', url: '#recap',
depth: 0, depth: 0,
@ -83,10 +82,7 @@ function getTableOfContents(children) {
} }
return { return {
url: '#' + child.props.id, url: '#' + child.props.id,
depth: depth: (child.type && parseInt(child.type.replace('h', ''), 0)) ?? 0,
(child.type?.mdxName &&
parseInt(child.type.mdxName.replace('h', ''), 0)) ??
0,
text: child.props.children, text: child.props.children,
}; };
}); });

Loading…
Cancel
Save