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.
*/
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,
},
};

22
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(<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,
};
});

Loading…
Cancel
Save