Skip to content

Commit 8b98216

Browse files
committed
Add MDX glob import demo to RSC playground
1 parent f533d2e commit 8b98216

File tree

16 files changed

+286
-10
lines changed

16 files changed

+286
-10
lines changed

packages/react-router/index-react-server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export { createStaticHandler } from "./lib/router/router";
4747
export {
4848
data,
4949
matchRoutes,
50+
isRouteErrorResponse,
5051
unstable_createContext,
5152
unstable_RouterContextProvider,
5253
} from "./lib/router/utils";

playground/rsc-vite-framework/app/root.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Meta, Link, Outlet } from "react-router";
1+
import type { Route } from "./+types/root";
2+
3+
import { Meta, Link, Outlet, isRouteErrorResponse } from "react-router";
24
import "./root.css";
35

46
export const meta = () => [{ title: "React Router Vite" }];
@@ -37,6 +39,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
3739
<li>
3840
<Link to="/mdx">MDX</Link>
3941
</li>
42+
<li>
43+
<Link to="/mdx-glob">MDX glob</Link>
44+
</li>
4045
</ul>
4146
</nav>
4247
</header>
@@ -55,6 +60,14 @@ export function ServerComponent() {
5560
);
5661
}
5762

58-
export function ErrorBoundary() {
59-
return <h1>Oooops</h1>;
63+
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
64+
return (
65+
<h1>
66+
{isRouteErrorResponse(error)
67+
? `${error.status} ${error.statusText}`
68+
: error instanceof Error
69+
? error.message
70+
: "Unknown Error"}
71+
</h1>
72+
);
6073
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.root {
2+
color: green;
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import styles from "./hello-component.module.css";
2+
3+
export function HelloComponent() {
4+
return <p className={styles.root}>Hello Component</p>;
5+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: Hello
3+
---
4+
5+
import { HelloComponent } from "./hello-component";
6+
7+
# Hello
8+
9+
This is a blog post written in MDX.
10+
11+
<HelloComponent />
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
type BlogPost = {
2+
Component: React.ComponentType;
3+
title: string;
4+
slug: string;
5+
path: string;
6+
};
7+
8+
async function resolvePosts(): Promise<
9+
Record<string, () => Promise<BlogPost>>
10+
> {
11+
const rawPosts = (await import.meta.glob("./*/*.mdx")) as Record<
12+
string,
13+
() => Promise<{
14+
default: React.ComponentType;
15+
frontmatter: { title: string };
16+
}>
17+
>;
18+
19+
return Object.fromEntries(
20+
Object.entries(rawPosts).map(([path, loadPost]) => {
21+
const slug = path.split("/").pop()!.replace(".mdx", "");
22+
23+
return [
24+
slug,
25+
async (): Promise<BlogPost> => {
26+
const pathParts = path.split("/");
27+
const directoryName = pathParts[pathParts.length - 2];
28+
29+
if (directoryName !== slug) {
30+
throw new Error(
31+
`Invalid post structure: directory name "${directoryName}" does not match slug "${slug}" in path "${path}"`,
32+
);
33+
}
34+
35+
const post = await loadPost();
36+
37+
if (
38+
!post?.frontmatter ||
39+
typeof post.frontmatter !== "object" ||
40+
!("title" in post.frontmatter) ||
41+
typeof post.frontmatter.title !== "string"
42+
) {
43+
throw new Error(`Invalid frontmatter for ${path}`);
44+
}
45+
46+
return {
47+
Component: post.default,
48+
title: post.frontmatter.title,
49+
slug,
50+
path: `/mdx-glob/${slug}`,
51+
};
52+
},
53+
];
54+
}),
55+
);
56+
}
57+
58+
export async function getPost(slug: string): Promise<BlogPost | undefined> {
59+
const posts = await resolvePosts();
60+
const loadPost = posts[slug];
61+
62+
if (!loadPost) {
63+
return undefined;
64+
}
65+
66+
return await loadPost();
67+
}
68+
69+
export async function getPosts(): Promise<Record<string, BlogPost>> {
70+
const posts = await resolvePosts();
71+
72+
return Object.fromEntries(
73+
await Promise.all(
74+
Object.entries(posts).map(async ([slug, loadPost]) => {
75+
return [slug, await loadPost()];
76+
}),
77+
),
78+
);
79+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.root {
2+
color: rebeccapurple;
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import styles from "./world-component.module.css";
2+
3+
export function WorldComponent() {
4+
return <p className={styles.root}>World Component</p>;
5+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: World
3+
---
4+
5+
import { WorldComponent } from "./world-component";
6+
7+
# World
8+
9+
This is another blog post written in MDX.
10+
11+
<WorldComponent />
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { Route } from "./+types/route";
2+
import { getPost } from "./posts/posts";
3+
4+
export async function loader({ params }: Route.LoaderArgs) {
5+
const post = await getPost(params.post);
6+
7+
if (!post) {
8+
throw new Response("Not Found", { status: 404, statusText: "Not Found" });
9+
}
10+
11+
return { postElement: <post.Component /> };
12+
}
13+
14+
export function ServerComponent({ loaderData }: Route.ComponentProps) {
15+
return loaderData.postElement;
16+
}

0 commit comments

Comments
 (0)