Skip to content

Commit 5f0c400

Browse files
committed
convert mdx post processing to actual plugins
1 parent bb73bef commit 5f0c400

File tree

4 files changed

+176
-155
lines changed

4 files changed

+176
-155
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"react-collapsed": "4.0.4",
4141
"react-dom": "^19.0.0",
4242
"remark-frontmatter": "^4.0.1",
43-
"remark-gfm": "^3.0.1"
43+
"remark-gfm": "^3.0.1",
44+
"unist-builder": "^4.0.0"
4445
},
4546
"devDependencies": {
4647
"@babel/core": "^7.12.9",

src/utils/compileMDX.tsx

Lines changed: 162 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,140 @@ import * as runtime from 'react/jsx-runtime';
66
import {remarkPlugins} from '../../plugins/markdownToHtml';
77
import remarkGfm from 'remark-gfm';
88
import remarkFrontmatter from 'remark-frontmatter';
9-
import {prepareMDX} from './prepareMDX'; // Assuming prepareMDX is modularized
109
import {MDXComponents} from '../components/MDX/MDXComponents'; // Assuming MDXComponents is modularized
1110
import visit from 'unist-util-visit';
11+
import {u} from 'unist-builder';
1212

1313
const DISK_CACHE_BREAKER = 11;
1414

15+
export function remarkTOCExtractor({maxDepth = Infinity} = {}) {
16+
return (tree, file) => {
17+
const toc = [];
18+
19+
visit(tree, (node) => {
20+
// Standard markdown headings
21+
if (node.type === 'heading') {
22+
if (node.depth > maxDepth) {
23+
return;
24+
}
25+
const text = node.children
26+
.filter((child) => child.type === 'text')
27+
.map((child) => child.value)
28+
.join('');
29+
const id =
30+
node.data?.hProperties?.id || text.toLowerCase().replace(/\s+/g, '-');
31+
32+
toc.push({
33+
depth: node.depth,
34+
text,
35+
url: `#${id}`,
36+
});
37+
}
38+
39+
// MDX custom components (e.g., <TeamMember>)
40+
else if (node.type === 'mdxJsxFlowElement') {
41+
switch (node.name) {
42+
case 'TeamMember': {
43+
// Extract attributes like name, permalink, etc.
44+
let name = 'Team Member';
45+
let permalink = 'team-member';
46+
47+
if (Array.isArray(node.attributes)) {
48+
for (const attr of node.attributes) {
49+
if (attr.name === 'name' && attr.value) {
50+
name = attr.value;
51+
} else if (attr.name === 'permalink' && attr.value) {
52+
permalink = attr.value;
53+
}
54+
}
55+
}
56+
57+
toc.push({
58+
url: `#${permalink}`,
59+
depth: 3,
60+
text: name,
61+
});
62+
break;
63+
}
64+
65+
// Similarly handle <Challenges>, <Recap>, or any other custom tags if needed
66+
case 'Challenges':
67+
toc.push({
68+
url: '#challenges',
69+
depth: 2,
70+
text: 'Challenges',
71+
});
72+
break;
73+
case 'Recap':
74+
toc.push({
75+
url: '#recap',
76+
depth: 2,
77+
text: 'Recap',
78+
});
79+
break;
80+
default:
81+
break;
82+
}
83+
}
84+
});
85+
86+
// Insert "Overview" at the top if there's at least one heading
87+
if (toc.length > 0) {
88+
toc.unshift({
89+
url: '#',
90+
text: 'Overview',
91+
depth: 2,
92+
});
93+
}
94+
95+
file.data.toc = toc;
96+
};
97+
}
98+
99+
function remarkWrapElements() {
100+
const fullWidthTypes = [
101+
'Sandpack',
102+
'FullWidth',
103+
'Illustration',
104+
'IllustrationBlock',
105+
'Challenges',
106+
'Recipes',
107+
];
108+
109+
return (tree) => {
110+
const newChildren = [];
111+
let wrapQueue = [];
112+
113+
function flushWrapper() {
114+
if (wrapQueue.length > 0) {
115+
newChildren.push(
116+
u('mdxJsxFlowElement', {
117+
name: 'MaxWidth',
118+
attributes: [],
119+
children: wrapQueue,
120+
})
121+
);
122+
wrapQueue = [];
123+
}
124+
}
125+
126+
for (const node of tree.children) {
127+
if (
128+
node.type === 'mdxJsxFlowElement' &&
129+
fullWidthTypes.includes(node.name)
130+
) {
131+
flushWrapper();
132+
newChildren.push(node);
133+
} else {
134+
wrapQueue.push(node);
135+
}
136+
}
137+
flushWrapper();
138+
139+
tree.children = newChildren;
140+
};
141+
}
142+
15143
export default async function compileMDX(
16144
mdx: string,
17145
path: string | string[],
@@ -44,46 +172,48 @@ export default async function compileMDX(
44172
}
45173

46174
// Compile the MDX source code
47-
const code = String(
48-
await compile(mdx, {
49-
remarkPlugins: [...remarkPlugins, remarkGfm, remarkFrontmatter],
50-
51-
rehypePlugins: [
52-
// Support stuff like ```js App.js {1-5} active by passing it through.
53-
function rehypeMetaAsAttributes() {
54-
return (tree) => {
55-
visit(tree, 'element', (node) => {
56-
if (
57-
// @ts-expect-error -- tagName is a valid property
58-
node.tagName === 'code' &&
59-
node.data &&
60-
node.data.meta
61-
) {
62-
// @ts-expect-error -- properties is a valid property
63-
node.properties.meta = node.data.meta;
64-
}
65-
});
66-
};
67-
},
68-
],
69-
outputFormat: 'function-body',
70-
})
71-
);
175+
const code = await compile(mdx, {
176+
remarkPlugins: [
177+
...remarkPlugins,
178+
remarkGfm,
179+
remarkFrontmatter,
180+
remarkTOCExtractor,
181+
remarkWrapElements,
182+
],
183+
184+
rehypePlugins: [
185+
// Support stuff like ```js App.js {1-5} active by passing it through.
186+
function rehypeMetaAsAttributes() {
187+
return (tree) => {
188+
visit(tree, 'element', (node) => {
189+
if (
190+
// @ts-expect-error -- tagName is a valid property
191+
node.tagName === 'code' &&
192+
node.data &&
193+
node.data.meta
194+
) {
195+
// @ts-expect-error -- properties is a valid property
196+
node.properties.meta = node.data.meta;
197+
}
198+
});
199+
};
200+
},
201+
],
202+
outputFormat: 'function-body',
203+
});
72204

73205
const {data: meta} = grayMatter(mdx);
74206

75-
const {default: MDXContent} = await run(code, {
207+
const {default: MDXContent} = await run(String(code), {
76208
...runtime,
77209
baseUrl: import.meta.url,
78210
});
79211

80-
const {toc, children} = prepareMDX(
81-
<MDXContent components={{...MDXComponents}} />
82-
);
212+
const content = <MDXContent components={{...MDXComponents}} />;
83213

84214
return {
85-
content: children,
86-
toc,
215+
content,
216+
toc: code.data.toc,
87217
meta,
88218
};
89219
}

src/utils/prepareMDX.js

Lines changed: 0 additions & 122 deletions
This file was deleted.

yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1724,6 +1724,11 @@
17241724
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz"
17251725
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
17261726

1727+
"@types/unist@^3.0.0":
1728+
version "3.0.3"
1729+
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
1730+
integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
1731+
17271732
"@typescript-eslint/eslint-plugin@^5.36.2":
17281733
version "5.36.2"
17291734
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz"
@@ -8087,6 +8092,13 @@ unist-builder@^3.0.0:
80878092
dependencies:
80888093
"@types/unist" "^2.0.0"
80898094

8095+
unist-builder@^4.0.0:
8096+
version "4.0.0"
8097+
resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-4.0.0.tgz#817b326c015a6f9f5e92bb55b8e8bc5e578fe243"
8098+
integrity sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg==
8099+
dependencies:
8100+
"@types/unist" "^3.0.0"
8101+
80908102
unist-util-generated@^1.0.0:
80918103
version "1.1.6"
80928104
resolved "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz"

0 commit comments

Comments
 (0)