From 98438da1bf2113349f1bd84c4c02e6df51c7c43a Mon Sep 17 00:00:00 2001
From: Remco Haszing
Date: Fri, 30 May 2025 10:34:28 +0200
Subject: [PATCH] Enhance the MDX setup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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.
---
src/mdx-components.tsx | 136 +++++++++++++++++++++++------------------
tsconfig.json | 2 +-
2 files changed, 77 insertions(+), 61 deletions(-)
diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx
index 2186c4997..3409981ba 100644
--- a/src/mdx-components.tsx
+++ b/src/mdx-components.tsx
@@ -1,8 +1,18 @@
import type { MDXComponents } from "mdx/types";
-import React, { ReactNode } from "react";
+import React from "react";
import { CodeExample } from "./components/code-example";
import Link from "next/link";
+declare module "mdx/types" {
+ // Augment the MDX types to make it understand React.
+ namespace JSX {
+ type Element = React.JSX.Element;
+ type ElementClass = React.JSX.ElementClass;
+ type ElementType = React.JSX.ElementType;
+ type IntrinsicElements = React.JSX.IntrinsicElements;
+ }
+}
+
function getTextContent(node: React.ReactNode): string {
if (typeof node === "string" || typeof node === "number") {
return String(node);
@@ -51,64 +61,70 @@ function createHeading(level: 1 | 2 | 3 | 4 | 5 | 6) {
};
}
+const components = {
+ // Allows customizing built-in components, e.g. to add styling.
+ // h1: ({ children }) =>
{children}
,
+
+ h2: createHeading(2),
+ h3: createHeading(3),
+ h4: createHeading(4),
+ h5: createHeading(5),
+ h6: createHeading(6),
+
+ a(props) {
+ return )} />;
+ },
+
+ code({ children }) {
+ if (typeof children !== "string") {
+ return {children};
+ }
+
+ if (children.startsWith("<")) {
+ return {children};
+ }
+
+ return (
+
+ {children
+ .split(/(<[^>]+>)/g)
+ .map((part, i) => (part.startsWith("<") && part.endsWith(">") ? {part} : part))}
+
+ );
+ },
+
+ pre(props) {
+ let child = React.Children.only(props.children) as React.ReactElement;
+ if (!child) return null;
+
+ // @ts-ignore
+ let { className, children: code } = child.props;
+ let lang = className ? className.replace("language-", "") : "";
+ let filename = undefined;
+
+ // Extract `[!code filename:…]` directives from the first line of code
+ let lines = code.split("\n");
+ let filenameRegex = /\[\!code filename\:(.+)\]/;
+ let match = lines[0].match(filenameRegex);
+ if (match) {
+ filename = match[1];
+ code = lines.splice(1).join("\n");
+ }
+
+ return (
+
+
+
+ );
+ },
+} satisfies MDXComponents;
+
+declare global {
+ // Provide type-safety of provided components inside MDX files.
+ type MDXProvidedComponents = typeof components;
+}
+
// This file is required to use MDX in `app` directory.
-export function useMDXComponents(components: MDXComponents): MDXComponents {
- return {
- // Allows customizing built-in components, e.g. to add styling.
- // h1: ({ children }) =>
{children}
,
- ...components,
-
- h2: createHeading(2),
- h3: createHeading(3),
- h4: createHeading(4),
- h5: createHeading(5),
- h6: createHeading(6),
-
- a(props: any) {
- return ;
- },
-
- code({ children }: { children: string | ReactNode }) {
- if (typeof children !== "string") {
- return {children};
- }
-
- if (children.startsWith("<")) {
- return {children};
- }
-
- return (
-
- {children
- .split(/(<[^>]+>)/g)
- .map((part, i) => (part.startsWith("<") && part.endsWith(">") ? {part} : part))}
-
- );
- },
-
- pre(props) {
- let child = React.Children.only(props.children) as React.ReactElement;
- if (!child) return null;
-
- // @ts-ignore
- let { className, children: code } = child.props;
- let lang = className ? className.replace("language-", "") : "";
- let filename = undefined;
-
- // Extract `[!code filename:…]` directives from the first line of code
- let lines = code.split("\n");
- let filenameRegex = /\[\!code filename\:(.+)\]/;
- let match = lines[0].match(filenameRegex);
- if (match) {
- filename = match[1];
- code = lines.splice(1).join("\n");
- }
-
- return (
-