Skip to content

Commit 2bc974e

Browse files
committed
Update to MDX v3
1 parent 0fafea8 commit 2bc974e

File tree

4 files changed

+81
-70
lines changed

4 files changed

+81
-70
lines changed

web/package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@docusaurus/tsconfig": "^3.7.0",
4747
"@types/mdast": "^4.0.4",
4848
"mdast": "^2.3.2",
49+
"mdast-util-mdx": "^3.0.0",
4950
"prettier": "^3.0.3",
5051
"prettier-plugin-tailwindcss": "^0.5.6",
5152
"remark-cli": "^11.0.0",

web/src/remark/auto-js-code.ts

Lines changed: 64 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -35,76 +35,86 @@ Output:
3535
3636
*/
3737

38-
import assert from 'assert/strict'
39-
import { Code, Parents, Root, RootContent } from 'mdast'
40-
import * as path from 'path'
38+
import type * as md from 'mdast'
39+
import type { } from 'mdast-util-mdx'; // Type-only empty import to register MDX types into mdast
40+
import assert from 'node:assert/strict'
41+
import * as path from 'node:path'
4142
import * as prettier from 'prettier'
4243
import tsBlankSpace from 'ts-blank-space'
43-
import { Plugin } from 'unified'
44+
import type { Plugin } from 'unified'
4445
import { visitParents } from 'unist-util-visit-parents'
4546

4647
// Wrapped in \b to denote a word boundary
4748
const META_FLAG_REGEX = /\bauto-js\b/
4849
const SUPPORTED_LANGS = new Set(['ts', 'tsx'])
4950

50-
const autoJSCodePlugin: Plugin<[], Root> = () => {
51-
return async (tree, file) => {
52-
const nodesToProcess = new Set<{ node: Code; ancestors: Parents[] }>()
51+
const autoJSCodePlugin: Plugin<[], md.Root> = () => async (tree, file) => {
52+
const nodesToProcess = new Set<{ node: md.Code; ancestors: md.Parents[] }>()
5353

54-
visitParents(tree, 'code', (node, ancestors) => {
55-
if (node.meta && META_FLAG_REGEX.test(node.meta)) {
56-
if (!node.lang || !SUPPORTED_LANGS.has(node.lang)) {
57-
throw new Error(`Unsupported language: ${node.lang}`)
58-
}
59-
60-
// We put these aside for processing later
61-
// because `visitParents` does not allow
62-
// async visitors.
63-
nodesToProcess.add({ node, ancestors })
54+
visitParents(tree, 'code', (node, ancestors) => {
55+
if (node.meta && META_FLAG_REGEX.test(node.meta)) {
56+
if (!node.lang || !SUPPORTED_LANGS.has(node.lang)) {
57+
throw new Error(`Unsupported language: ${node.lang}`)
6458
}
65-
})
6659

67-
for (const { node, ancestors } of nodesToProcess) {
68-
const parent = ancestors.at(-1)
69-
assert(parent) // It must have a parent because the root node is a fully formed tree
70-
assert(node.meta && node.lang) // Already checked in the visitor
60+
// We put these aside for processing later
61+
// because `visitParents` does not allow
62+
// async visitors.
63+
nodesToProcess.add({ node, ancestors })
64+
}
65+
})
7166

72-
// Remove our flag from the meta so other plugins don't trip up
73-
const newMeta = node.meta.replace(META_FLAG_REGEX, '')
67+
for (const { node, ancestors } of nodesToProcess) {
68+
const parent = ancestors.at(-1)
69+
assert(parent) // It must have a parent because the root node is a fully formed tree
70+
assert(node.meta && node.lang) // Already checked in the visitor
7471

75-
const jsCodeBlock = await makeJsCodeBlock(newMeta, node, {
76-
location: file.path,
77-
})
78-
const tsCodeBlock = await makeTsCodeBlock(newMeta, node, {
79-
location: file.path,
80-
})
72+
// Remove our flag from the meta so other plugins don't trip up
73+
const newMeta = node.meta.replace(META_FLAG_REGEX, '')
8174

82-
const newNodes: RootContent[] = [
83-
{
84-
// @ts-expect-error This is an MDX extension
85-
type: 'jsx',
86-
value: `<Tabs groupId="js-ts"><TabItem value="js" label="JavaScript">`,
87-
},
88-
jsCodeBlock,
75+
const jsCodeBlock = await makeJsCodeBlock(newMeta, node, {
76+
location: file.path,
77+
})
78+
const tsCodeBlock = await makeTsCodeBlock(newMeta, node, {
79+
location: file.path,
80+
})
81+
82+
// The specific structure of the new node was retrieved by copy-pasting
83+
// an example into the MDX playground and inspecting the AST.
84+
// https://mdxjs.com/playground
85+
const newNode: md.RootContent = {
86+
type: 'mdxJsxFlowElement',
87+
name: 'Tabs',
88+
attributes: [
89+
{ type: 'mdxJsxAttribute', name: 'groupId', value: 'js-ts' },
90+
],
91+
children: [
8992
{
90-
// @ts-expect-error This is an MDX extension
91-
type: 'jsx',
92-
value: `</TabItem><TabItem value="ts" label="TypeScript">`,
93+
type: 'mdxJsxFlowElement',
94+
name: 'TabItem',
95+
attributes: [
96+
{ type: 'mdxJsxAttribute', name: 'value', value: 'js' },
97+
{ type: 'mdxJsxAttribute', name: 'label', value: 'JavaScript' },
98+
],
99+
children: [jsCodeBlock],
93100
},
94-
tsCodeBlock,
95101
{
96-
// @ts-expect-error This is an MDX extension
97-
type: 'jsx',
98-
value: `</TabItem></Tabs>`,
102+
type: 'mdxJsxFlowElement',
103+
name: 'TabItem',
104+
attributes: [
105+
{ type: 'mdxJsxAttribute', name: 'value', value: 'ts' },
106+
{ type: 'mdxJsxAttribute', name: 'label', value: 'TypeScript' },
107+
],
108+
children: [tsCodeBlock],
99109
},
100-
]
110+
],
111+
}
101112

102-
const idx = parent.children.findIndex((someNode) => someNode === node)
103-
assert(idx !== -1, "Node not found in parent's children")
113+
const idx = parent.children.findIndex((someNode) => someNode === node)
114+
assert(idx !== -1, "Node not found in parent's children")
104115

105-
// Replace input node for the new ones in the parent's children array
106-
parent.children.splice(idx, 1, ...newNodes)
107-
}
116+
// Replace input node for the new ones in the parent's children array
117+
parent.children.splice(idx, 1, newNode)
108118
}
109119
}
110120

@@ -116,9 +126,9 @@ const CODE_BLOCK_TITLE_REGEX = /title=(?<quote>["'])(?<title>.*?)\1/
116126

117127
async function makeJsCodeBlock(
118128
metaString: string,
119-
node: Code,
129+
node: md.Code,
120130
{ location }: { location: string }
121-
): Promise<RootContent> {
131+
): Promise<md.Code> {
122132
// Find the `title=` meta param and change the extension
123133
const meta = metaString.replace(
124134
CODE_BLOCK_TITLE_REGEX,
@@ -143,9 +153,9 @@ async function makeJsCodeBlock(
143153

144154
async function makeTsCodeBlock(
145155
metaString: string,
146-
node: Code,
156+
node: md.Code,
147157
{ location }: { location: string }
148-
): Promise<RootContent> {
158+
): Promise<md.Code> {
149159
const lang = node.lang
150160
const code = await format(node.value, { parser: 'babel-ts', location })
151161

web/src/remark/code-with-hole.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ Output:
2222
2323
*/
2424

25-
import { Root } from 'mdast'
26-
import { Plugin } from 'unified'
25+
import type * as md from 'mdast'
26+
import type { Plugin } from 'unified'
2727
import { visitParents } from 'unist-util-visit-parents'
2828

2929
// Wrapped in \b to denote a word boundary
@@ -33,22 +33,20 @@ const HOLE_REPLACEMENT = '/* ... */'
3333

3434
const SUPPORTED_LANGS = new Set(['js', 'jsx', 'ts', 'tsx'])
3535

36-
const codeWithHolePlugin: Plugin<[], Root> = () => {
37-
return (tree) => {
38-
visitParents(tree, 'code', (node) => {
39-
if (node.meta && META_FLAG_REGEX.test(node.meta)) {
40-
if (!node.lang || !SUPPORTED_LANGS.has(node.lang)) {
41-
throw new Error(`Unsupported language: ${node.lang}`)
42-
}
36+
const codeWithHolePlugin: Plugin<[], md.Root> = () => (tree) => {
37+
visitParents(tree, 'code', (node) => {
38+
if (node.meta && META_FLAG_REGEX.test(node.meta)) {
39+
if (!node.lang || !SUPPORTED_LANGS.has(node.lang)) {
40+
throw new Error(`Unsupported language: ${node.lang}`)
41+
}
4342

44-
// Remove our flag from the meta so other plugins don't trip up
45-
node.meta = node.meta.replace(META_FLAG_REGEX, '')
43+
// Remove our flag from the meta so other plugins don't trip up
44+
node.meta = node.meta.replace(META_FLAG_REGEX, '')
4645

47-
// Replace hole with ellipsis
48-
node.value = node.value.replace(HOLE_IDENTIFIER_REGEX, HOLE_REPLACEMENT)
49-
}
50-
})
51-
}
46+
// Replace hole with ellipsis
47+
node.value = node.value.replace(HOLE_IDENTIFIER_REGEX, HOLE_REPLACEMENT)
48+
}
49+
})
5250
}
5351

5452
export default codeWithHolePlugin

0 commit comments

Comments
 (0)