Skip to content

Commit b6bc1a9

Browse files
Add MDX glob import demo to RSC playground (#14172)
1 parent 9348785 commit b6bc1a9

File tree

15 files changed

+293
-14
lines changed

15 files changed

+293
-14
lines changed

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: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import nodePath from "node:path";
2+
3+
type BlogPost = {
4+
Component: React.ComponentType;
5+
path: string;
6+
title: string;
7+
slug: string;
8+
};
9+
10+
async function resolvePosts(): Promise<{
11+
[slug: string]: () => Promise<BlogPost>;
12+
}> {
13+
const rawPosts = (await import.meta.glob("./*/*.mdx")) as Record<
14+
string,
15+
() => Promise<{
16+
default: React.ComponentType;
17+
frontmatter?: unknown;
18+
}>
19+
>;
20+
21+
return Object.fromEntries(
22+
Object.entries(rawPosts).map(([importPath, loadPost]) => {
23+
const slug = importPath.split("/").pop()!.replace(".mdx", "");
24+
25+
return [
26+
slug,
27+
async (): Promise<BlogPost> => {
28+
const post = await loadPost();
29+
30+
let title: string;
31+
if (
32+
post.frontmatter &&
33+
typeof post.frontmatter === "object" &&
34+
"title" in post.frontmatter &&
35+
typeof post.frontmatter.title === "string"
36+
) {
37+
title = post.frontmatter.title;
38+
} else {
39+
const prettyPath = nodePath.relative(
40+
process.cwd(),
41+
nodePath.resolve(import.meta.dirname, importPath),
42+
);
43+
console.error(
44+
`Invalid frontmatter for ${prettyPath}: Missing title`,
45+
);
46+
title = "Untitled Post";
47+
}
48+
49+
return {
50+
Component: post.default,
51+
path: `/mdx-glob/${slug}`,
52+
title,
53+
slug,
54+
};
55+
},
56+
];
57+
}),
58+
);
59+
}
60+
61+
export async function getPost(slug: string): Promise<BlogPost | null> {
62+
const posts = await resolvePosts();
63+
const loadPost = posts[slug];
64+
return loadPost ? await loadPost() : null;
65+
}
66+
67+
export async function getPosts(): Promise<Record<string, BlogPost>> {
68+
const posts = await resolvePosts();
69+
return Object.fromEntries(
70+
await Promise.all(
71+
Object.entries(posts).map(async ([slug, loadPost]) => {
72+
return [slug, await loadPost()];
73+
}),
74+
),
75+
);
76+
}
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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 {
12+
title: post.title,
13+
element: <post.Component />,
14+
};
15+
}
16+
17+
export function meta({ loaderData }: Route.MetaArgs) {
18+
return [{ title: loaderData.title }];
19+
}
20+
21+
export function ServerComponent({ loaderData }: Route.ComponentProps) {
22+
return loaderData.element;
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Link } from "react-router";
2+
import { getPosts } from "../mdx-glob.$post/posts/posts";
3+
4+
export async function ServerComponent() {
5+
const posts = await getPosts();
6+
7+
return (
8+
<>
9+
<h1>MDX glob</h1>
10+
<ul>
11+
{Object.values(posts).map(({ path, title }) => (
12+
<li key={path}>
13+
<Link to={path}>{title}</Link>
14+
</li>
15+
))}
16+
</ul>
17+
</>
18+
);
19+
}

0 commit comments

Comments
 (0)