Skip to content

Commit 5334891

Browse files
authored
Improve MDX setup (#2265)
This fixes a couple of things. - The argument was removed from `useMDXComponents()`. In reality this function doesn’t receive any arguments. - Components overrides are defined outside of `useMDXComponents()`. `useMDXComponents()` is called during render. Defining React components during render causes unnecessary re-renders and loss of state. - The `**/*.mdx` glob is included in the `tsconfig.json` include patterns. This makes `.mdx` files part of the same TypeScript program in the editor. This leads to a better editor experience and reduced memory and CPU usage. - This augments the `mdx/types` module to define the `JSX` namespace. React 18 defined the global `JSX` namespace. React 19 no longer does this. This leads to type errors in `mdx/types` resulting unresolved `any` types. This augmentation makes the MDX types understand React.
1 parent c092755 commit 5334891

File tree

2 files changed

+77
-61
lines changed

2 files changed

+77
-61
lines changed

src/mdx-components.tsx

Lines changed: 76 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import type { MDXComponents } from "mdx/types";
2-
import React, { ReactNode } from "react";
2+
import React from "react";
33
import { CodeExample } from "./components/code-example";
44
import Link from "next/link";
55

6+
declare module "mdx/types" {
7+
// Augment the MDX types to make it understand React.
8+
namespace JSX {
9+
type Element = React.JSX.Element;
10+
type ElementClass = React.JSX.ElementClass;
11+
type ElementType = React.JSX.ElementType;
12+
type IntrinsicElements = React.JSX.IntrinsicElements;
13+
}
14+
}
15+
616
function getTextContent(node: React.ReactNode): string {
717
if (typeof node === "string" || typeof node === "number") {
818
return String(node);
@@ -51,64 +61,70 @@ function createHeading(level: 1 | 2 | 3 | 4 | 5 | 6) {
5161
};
5262
}
5363

64+
const components = {
65+
// Allows customizing built-in components, e.g. to add styling.
66+
// h1: ({ children }) => <h1 style={{ fontSize: "100px" }}>{children}</h1>,
67+
68+
h2: createHeading(2),
69+
h3: createHeading(3),
70+
h4: createHeading(4),
71+
h5: createHeading(5),
72+
h6: createHeading(6),
73+
74+
a(props) {
75+
return <Link {...(props as React.ComponentProps<typeof Link>)} />;
76+
},
77+
78+
code({ children }) {
79+
if (typeof children !== "string") {
80+
return <code>{children}</code>;
81+
}
82+
83+
if (children.startsWith("<")) {
84+
return <code>{children}</code>;
85+
}
86+
87+
return (
88+
<code>
89+
{children
90+
.split(/(<[^>]+>)/g)
91+
.map((part, i) => (part.startsWith("<") && part.endsWith(">") ? <var key={i}>{part}</var> : part))}
92+
</code>
93+
);
94+
},
95+
96+
pre(props) {
97+
let child = React.Children.only(props.children) as React.ReactElement;
98+
if (!child) return null;
99+
100+
// @ts-ignore
101+
let { className, children: code } = child.props;
102+
let lang = className ? className.replace("language-", "") : "";
103+
let filename = undefined;
104+
105+
// Extract `[!code filename:…]` directives from the first line of code
106+
let lines = code.split("\n");
107+
let filenameRegex = /\[\!code filename\:(.+)\]/;
108+
let match = lines[0].match(filenameRegex);
109+
if (match) {
110+
filename = match[1];
111+
code = lines.splice(1).join("\n");
112+
}
113+
114+
return (
115+
<div>
116+
<CodeExample example={{ lang, code }} className="not-prose" filename={filename} />
117+
</div>
118+
);
119+
},
120+
} satisfies MDXComponents;
121+
122+
declare global {
123+
// Provide type-safety of provided components inside MDX files.
124+
type MDXProvidedComponents = typeof components;
125+
}
126+
54127
// This file is required to use MDX in `app` directory.
55-
export function useMDXComponents(components: MDXComponents): MDXComponents {
56-
return {
57-
// Allows customizing built-in components, e.g. to add styling.
58-
// h1: ({ children }) => <h1 style={{ fontSize: "100px" }}>{children}</h1>,
59-
...components,
60-
61-
h2: createHeading(2),
62-
h3: createHeading(3),
63-
h4: createHeading(4),
64-
h5: createHeading(5),
65-
h6: createHeading(6),
66-
67-
a(props: any) {
68-
return <Link {...props} />;
69-
},
70-
71-
code({ children }: { children: string | ReactNode }) {
72-
if (typeof children !== "string") {
73-
return <code>{children}</code>;
74-
}
75-
76-
if (children.startsWith("<")) {
77-
return <code>{children}</code>;
78-
}
79-
80-
return (
81-
<code>
82-
{children
83-
.split(/(<[^>]+>)/g)
84-
.map((part, i) => (part.startsWith("<") && part.endsWith(">") ? <var key={i}>{part}</var> : part))}
85-
</code>
86-
);
87-
},
88-
89-
pre(props) {
90-
let child = React.Children.only(props.children) as React.ReactElement;
91-
if (!child) return null;
92-
93-
// @ts-ignore
94-
let { className, children: code } = child.props;
95-
let lang = className ? className.replace("language-", "") : "";
96-
let filename = undefined;
97-
98-
// Extract `[!code filename:…]` directives from the first line of code
99-
let lines = code.split("\n");
100-
let filenameRegex = /\[\!code filename\:(.+)\]/;
101-
let match = lines[0].match(filenameRegex);
102-
if (match) {
103-
filename = match[1];
104-
code = lines.splice(1).join("\n");
105-
}
106-
107-
return (
108-
<div>
109-
<CodeExample example={{ lang, code }} className="not-prose" filename={filename} />
110-
</div>
111-
);
112-
},
113-
};
128+
export function useMDXComponents(): MDXProvidedComponents {
129+
return components;
114130
}

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@
2222
"@/*": ["./src/*"]
2323
}
2424
},
25-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25+
"include": ["next-env.d.ts", "**/*.mdx", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
2626
"exclude": ["node_modules"]
2727
}

0 commit comments

Comments
 (0)