From 8b98216dc0ccb4f6ae2e99ed3f928977b3138cf7 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Wed, 13 Aug 2025 16:13:27 +1000 Subject: [PATCH 1/8] Add MDX glob import demo to RSC playground --- packages/react-router/index-react-server.ts | 1 + playground/rsc-vite-framework/app/root.tsx | 19 +++- .../posts/hello/hello-component.module.css | 3 + .../posts/hello/hello-component.tsx | 5 + .../mdx-glob.$post/posts/hello/hello.mdx | 11 ++ .../app/routes/mdx-glob.$post/posts/posts.ts | 79 ++++++++++++++ .../posts/world/world-component.module.css | 3 + .../posts/world/world-component.tsx | 5 + .../mdx-glob.$post/posts/world/world.mdx | 11 ++ .../app/routes/mdx-glob.$post/route.tsx | 16 +++ .../app/routes/mdx-glob._index/route.tsx | 19 ++++ .../app/routes/mdx/message.tsx | 6 ++ .../app/routes/mdx/route.mdx | 7 +- playground/rsc-vite-framework/package.json | 2 + playground/rsc-vite-framework/vite.config.ts | 9 +- pnpm-lock.yaml | 100 ++++++++++++++++++ 16 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello-component.module.css create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello-component.tsx create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello.mdx create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world-component.module.css create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world-component.tsx create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world.mdx create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx create mode 100644 playground/rsc-vite-framework/app/routes/mdx-glob._index/route.tsx create mode 100644 playground/rsc-vite-framework/app/routes/mdx/message.tsx diff --git a/packages/react-router/index-react-server.ts b/packages/react-router/index-react-server.ts index fe245336e0..0e4bf1230d 100644 --- a/packages/react-router/index-react-server.ts +++ b/packages/react-router/index-react-server.ts @@ -47,6 +47,7 @@ export { createStaticHandler } from "./lib/router/router"; export { data, matchRoutes, + isRouteErrorResponse, unstable_createContext, unstable_RouterContextProvider, } from "./lib/router/utils"; diff --git a/playground/rsc-vite-framework/app/root.tsx b/playground/rsc-vite-framework/app/root.tsx index 72b5478532..5d7e0ad234 100644 --- a/playground/rsc-vite-framework/app/root.tsx +++ b/playground/rsc-vite-framework/app/root.tsx @@ -1,4 +1,6 @@ -import { Meta, Link, Outlet } from "react-router"; +import type { Route } from "./+types/root"; + +import { Meta, Link, Outlet, isRouteErrorResponse } from "react-router"; import "./root.css"; export const meta = () => [{ title: "React Router Vite" }]; @@ -37,6 +39,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
  • MDX
  • +
  • + MDX glob +
  • @@ -55,6 +60,14 @@ export function ServerComponent() { ); } -export function ErrorBoundary() { - return

    Oooops

    ; +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + return ( +

    + {isRouteErrorResponse(error) + ? `${error.status} ${error.statusText}` + : error instanceof Error + ? error.message + : "Unknown Error"} +

    + ); } diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello-component.module.css b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello-component.module.css new file mode 100644 index 0000000000..211cf429c9 --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello-component.module.css @@ -0,0 +1,3 @@ +.root { + color: green; +} diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello-component.tsx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello-component.tsx new file mode 100644 index 0000000000..3efde48566 --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello-component.tsx @@ -0,0 +1,5 @@ +import styles from "./hello-component.module.css"; + +export function HelloComponent() { + return

    Hello Component

    ; +} diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello.mdx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello.mdx new file mode 100644 index 0000000000..631f1dba5f --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/hello/hello.mdx @@ -0,0 +1,11 @@ +--- +title: Hello +--- + +import { HelloComponent } from "./hello-component"; + +# Hello + +This is a blog post written in MDX. + + diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts new file mode 100644 index 0000000000..e8dafec624 --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts @@ -0,0 +1,79 @@ +type BlogPost = { + Component: React.ComponentType; + title: string; + slug: string; + path: string; +}; + +async function resolvePosts(): Promise< + Record Promise> +> { + const rawPosts = (await import.meta.glob("./*/*.mdx")) as Record< + string, + () => Promise<{ + default: React.ComponentType; + frontmatter: { title: string }; + }> + >; + + return Object.fromEntries( + Object.entries(rawPosts).map(([path, loadPost]) => { + const slug = path.split("/").pop()!.replace(".mdx", ""); + + return [ + slug, + async (): Promise => { + const pathParts = path.split("/"); + const directoryName = pathParts[pathParts.length - 2]; + + if (directoryName !== slug) { + throw new Error( + `Invalid post structure: directory name "${directoryName}" does not match slug "${slug}" in path "${path}"`, + ); + } + + const post = await loadPost(); + + if ( + !post?.frontmatter || + typeof post.frontmatter !== "object" || + !("title" in post.frontmatter) || + typeof post.frontmatter.title !== "string" + ) { + throw new Error(`Invalid frontmatter for ${path}`); + } + + return { + Component: post.default, + title: post.frontmatter.title, + slug, + path: `/mdx-glob/${slug}`, + }; + }, + ]; + }), + ); +} + +export async function getPost(slug: string): Promise { + const posts = await resolvePosts(); + const loadPost = posts[slug]; + + if (!loadPost) { + return undefined; + } + + return await loadPost(); +} + +export async function getPosts(): Promise> { + const posts = await resolvePosts(); + + return Object.fromEntries( + await Promise.all( + Object.entries(posts).map(async ([slug, loadPost]) => { + return [slug, await loadPost()]; + }), + ), + ); +} diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world-component.module.css b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world-component.module.css new file mode 100644 index 0000000000..9e98c98f1e --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world-component.module.css @@ -0,0 +1,3 @@ +.root { + color: rebeccapurple; +} diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world-component.tsx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world-component.tsx new file mode 100644 index 0000000000..f10ed656e6 --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world-component.tsx @@ -0,0 +1,5 @@ +import styles from "./world-component.module.css"; + +export function WorldComponent() { + return

    World Component

    ; +} diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world.mdx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world.mdx new file mode 100644 index 0000000000..9e077e109d --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/world/world.mdx @@ -0,0 +1,11 @@ +--- +title: World +--- + +import { WorldComponent } from "./world-component"; + +# World + +This is another blog post written in MDX. + + diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx new file mode 100644 index 0000000000..185d07ab89 --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx @@ -0,0 +1,16 @@ +import type { Route } from "./+types/route"; +import { getPost } from "./posts/posts"; + +export async function loader({ params }: Route.LoaderArgs) { + const post = await getPost(params.post); + + if (!post) { + throw new Response("Not Found", { status: 404, statusText: "Not Found" }); + } + + return { postElement: }; +} + +export function ServerComponent({ loaderData }: Route.ComponentProps) { + return loaderData.postElement; +} diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob._index/route.tsx b/playground/rsc-vite-framework/app/routes/mdx-glob._index/route.tsx new file mode 100644 index 0000000000..3567e2c864 --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx-glob._index/route.tsx @@ -0,0 +1,19 @@ +import { Link } from "react-router"; +import { getPosts } from "../mdx-glob.$post/posts/posts"; + +export async function ServerComponent() { + const posts = await getPosts(); + + return ( + <> +

    MDX glob

    +
      + {Object.values(posts).map(({ path, title }) => ( +
    • + {title} +
    • + ))} +
    + + ); +} diff --git a/playground/rsc-vite-framework/app/routes/mdx/message.tsx b/playground/rsc-vite-framework/app/routes/mdx/message.tsx new file mode 100644 index 0000000000..40f36049bd --- /dev/null +++ b/playground/rsc-vite-framework/app/routes/mdx/message.tsx @@ -0,0 +1,6 @@ +import { useLoaderData } from "react-router"; + +export function Message() { + const { message } = useLoaderData(); + return
    Loader data: {message}
    ; +} diff --git a/playground/rsc-vite-framework/app/routes/mdx/route.mdx b/playground/rsc-vite-framework/app/routes/mdx/route.mdx index a3d6fd31ae..821ff29a95 100644 --- a/playground/rsc-vite-framework/app/routes/mdx/route.mdx +++ b/playground/rsc-vite-framework/app/routes/mdx/route.mdx @@ -1,14 +1,9 @@ -import { useLoaderData } from "react-router"; +import { Message } from "./message"; export const meta = () => [{ title: "MDX Route" }]; export const loader = () => ({ message: "Loader data" }); -export function Message() { - const { message } = useLoaderData(); - return
    Loader data: {message}
    ; -} - # This is an MDX route Hello from an MDX route! diff --git a/playground/rsc-vite-framework/package.json b/playground/rsc-vite-framework/package.json index 2582e0aba4..ba68f82041 100644 --- a/playground/rsc-vite-framework/package.json +++ b/playground/rsc-vite-framework/package.json @@ -20,6 +20,8 @@ "@vitejs/plugin-react": "^4.5.2", "@vitejs/plugin-rsc": "0.4.11", "cross-env": "^7.0.3", + "remark-frontmatter": "^5.0.0", + "remark-mdx-frontmatter": "^5.2.0", "typescript": "^5.1.6", "vite": "^6.2.0" }, diff --git a/playground/rsc-vite-framework/vite.config.ts b/playground/rsc-vite-framework/vite.config.ts index 6fd8fbb5fa..04c5efb49c 100644 --- a/playground/rsc-vite-framework/vite.config.ts +++ b/playground/rsc-vite-framework/vite.config.ts @@ -1,10 +1,17 @@ import { defineConfig } from "vite"; import { __INTERNAL_DO_NOT_USE_OR_YOU_WILL_GET_A_STRONGLY_WORDED_LETTER__ } from "@react-router/dev/internal"; import mdx from "@mdx-js/rollup"; +import remarkFrontmatter from "remark-frontmatter"; +import remarkMdxFrontmatter from "remark-mdx-frontmatter"; const { unstable_reactRouterRSC: reactRouterRSC } = __INTERNAL_DO_NOT_USE_OR_YOU_WILL_GET_A_STRONGLY_WORDED_LETTER__; export default defineConfig({ - plugins: [mdx(), reactRouterRSC()], + plugins: [ + mdx({ + remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter], + }), + reactRouterRSC(), + ], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de0d6d2f0c..d768583b78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1971,6 +1971,12 @@ importers: cross-env: specifier: ^7.0.3 version: 7.0.3 + remark-frontmatter: + specifier: ^5.0.0 + version: 5.0.0 + remark-mdx-frontmatter: + specifier: ^5.2.0 + version: 5.2.0 typescript: specifier: ^5.1.6 version: 5.4.5 @@ -6446,9 +6452,15 @@ packages: estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + estree-util-to-js@2.0.0: resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + estree-util-value-to-estree@3.4.0: + resolution: {integrity: sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==} + estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} @@ -6534,6 +6546,9 @@ packages: fastq@1.13.0: resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -6602,6 +6617,10 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + formidable@2.1.2: resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} deprecated: 'ACTION REQUIRED: SWITCH TO v3 - v1 and v2 are VULNERABLE! v1 is DEPRECATED FOR OVER 2 YEARS! Use formidable@latest or try formidable-mini for fresh projects' @@ -7637,6 +7656,9 @@ packages: mdast-util-from-markdown@2.0.0: resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==} + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + mdast-util-gfm-autolink-literal@1.0.3: resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==} @@ -7733,6 +7755,9 @@ packages: micromark-core-commonmark@2.0.0: resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} + micromark-extension-gfm-autolink-literal@1.0.5: resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} @@ -8701,9 +8726,15 @@ packages: resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} hasBin: true + remark-frontmatter@5.0.0: + resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} + remark-gfm@3.0.1: resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} + remark-mdx-frontmatter@5.2.0: + resolution: {integrity: sha512-U/hjUYTkQqNjjMRYyilJgLXSPF65qbLPdoESOkXyrwz2tVyhAnm4GUKhfXqOOS9W34M3545xEMq+aMpHgVjEeQ==} + remark-mdx@3.0.1: resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} @@ -9344,6 +9375,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -9575,6 +9609,9 @@ packages: unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-mdx-define@1.1.2: + resolution: {integrity: sha512-9ncH7i7TN5Xn7/tzX5bE3rXgz1X/u877gYVAUB3mLeTKYJmQHmqKTDBi6BTGXV7AeolBCI9ErcVsOt2qryoD0g==} + unist-util-position-from-estree@2.0.0: resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} @@ -15325,12 +15362,21 @@ snapshots: estree-util-is-identifier-name@3.0.0: {} + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-to-js@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 astring: 1.8.6 source-map: 0.7.4 + estree-util-value-to-estree@3.4.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-visit@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -15479,6 +15525,10 @@ snapshots: dependencies: reusify: 1.0.4 + fault@2.0.1: + dependencies: + format: 0.2.2 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -15563,6 +15613,8 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + format@0.2.2: {} + formidable@2.1.2: dependencies: dezalgo: 1.0.4 @@ -16845,6 +16897,17 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-frontmatter@2.0.1: + dependencies: + '@types/mdast': 4.0.3 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.0 + mdast-util-to-markdown: 2.1.0 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + mdast-util-gfm-autolink-literal@1.0.3: dependencies: '@types/mdast': 3.0.15 @@ -17065,6 +17128,13 @@ snapshots: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + micromark-extension-gfm-autolink-literal@1.0.5: dependencies: micromark-util-character: 1.2.0 @@ -18340,6 +18410,15 @@ snapshots: dependencies: jsesc: 3.0.2 + remark-frontmatter@5.0.0: + dependencies: + '@types/mdast': 4.0.3 + mdast-util-frontmatter: 2.0.1 + micromark-extension-frontmatter: 2.0.0 + unified: 11.0.4 + transitivePeerDependencies: + - supports-color + remark-gfm@3.0.1: dependencies: '@types/mdast': 3.0.15 @@ -18349,6 +18428,15 @@ snapshots: transitivePeerDependencies: - supports-color + remark-mdx-frontmatter@5.2.0: + dependencies: + '@types/mdast': 4.0.3 + estree-util-value-to-estree: 3.4.0 + toml: 3.0.0 + unified: 11.0.4 + unist-util-mdx-define: 1.1.2 + yaml: 2.8.0 + remark-mdx@3.0.1: dependencies: mdast-util-mdx: 3.0.0 @@ -19096,6 +19184,8 @@ snapshots: toidentifier@1.0.1: {} + toml@3.0.0: {} + tough-cookie@4.1.4: dependencies: psl: 1.9.0 @@ -19345,6 +19435,16 @@ snapshots: dependencies: '@types/unist': 3.0.2 + unist-util-mdx-define@1.1.2: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.3 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + vfile: 6.0.1 + unist-util-position-from-estree@2.0.0: dependencies: '@types/unist': 3.0.2 From 3dcb9d17b28320cc418dd4e90fe8b22960594276 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 14 Aug 2025 10:06:58 +1000 Subject: [PATCH 2/8] Refactor posts logic --- .../app/routes/mdx-glob.$post/posts/posts.ts | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts index e8dafec624..e8e32d50a8 100644 --- a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts @@ -1,18 +1,20 @@ +import nodePath from "node:path"; + type BlogPost = { Component: React.ComponentType; + path: string; title: string; slug: string; - path: string; }; -async function resolvePosts(): Promise< - Record Promise> -> { +async function resolvePosts(): Promise<{ + [slug: string]: () => Promise; +}> { const rawPosts = (await import.meta.glob("./*/*.mdx")) as Record< string, () => Promise<{ default: React.ComponentType; - frontmatter: { title: string }; + frontmatter?: unknown; }> >; @@ -23,31 +25,32 @@ async function resolvePosts(): Promise< return [ slug, async (): Promise => { - const pathParts = path.split("/"); - const directoryName = pathParts[pathParts.length - 2]; - - if (directoryName !== slug) { - throw new Error( - `Invalid post structure: directory name "${directoryName}" does not match slug "${slug}" in path "${path}"`, - ); - } - const post = await loadPost(); + let title: string; if ( - !post?.frontmatter || - typeof post.frontmatter !== "object" || - !("title" in post.frontmatter) || - typeof post.frontmatter.title !== "string" + post.frontmatter && + typeof post.frontmatter === "object" && + "title" in post.frontmatter && + typeof post.frontmatter.title === "string" ) { - throw new Error(`Invalid frontmatter for ${path}`); + title = post.frontmatter.title; + } else { + const prettyPath = nodePath.relative( + process.cwd(), + nodePath.resolve(import.meta.dirname, path), + ); + console.error( + `Invalid frontmatter for ${prettyPath}: Missing title`, + ); + title = "Untitled Post"; } return { Component: post.default, - title: post.frontmatter.title, - slug, path: `/mdx-glob/${slug}`, + title, + slug, }; }, ]; @@ -55,20 +58,14 @@ async function resolvePosts(): Promise< ); } -export async function getPost(slug: string): Promise { +export async function getPost(slug: string): Promise { const posts = await resolvePosts(); const loadPost = posts[slug]; - - if (!loadPost) { - return undefined; - } - - return await loadPost(); + return loadPost ? await loadPost() : null; } export async function getPosts(): Promise> { const posts = await resolvePosts(); - return Object.fromEntries( await Promise.all( Object.entries(posts).map(async ([slug, loadPost]) => { From 9f155c9070246e33eaa7f750b144cce292a5a8f6 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 14 Aug 2025 10:10:34 +1000 Subject: [PATCH 3/8] Dedupe lockfile --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 068cc92632..f043c4585c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,7 +113,7 @@ importers: version: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: specifier: next - version: 6.1.0-canary-1dc3bdea-20250812(eslint@8.57.0) + version: 6.1.0-canary-534bed5f-20250813(eslint@8.57.0) fast-glob: specifier: 3.2.11 version: 3.2.11 @@ -6370,8 +6370,8 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - eslint-plugin-react-hooks@6.1.0-canary-1dc3bdea-20250812: - resolution: {integrity: sha512-IurZGvEI/7dgFK7D1+fvjBqk5SR2srf/REbvgTf7RoUk3GOCdZt4yWXCEEMOhpVD0yX0rkMV/vVqfI2don9QvQ==} + eslint-plugin-react-hooks@6.1.0-canary-534bed5f-20250813: + resolution: {integrity: sha512-miWk2CFp5nb7BPt8/vktkVWTeft1JNNVNUAUpfBZTt4Q+6C0zjFvTaXdr0ae2/RmuaN7WsWCwN4kJGenJ8QQww==} engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 @@ -15222,7 +15222,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-react-hooks@6.1.0-canary-1dc3bdea-20250812(eslint@8.57.0): + eslint-plugin-react-hooks@6.1.0-canary-534bed5f-20250813(eslint@8.57.0): dependencies: '@babel/core': 7.27.7 '@babel/parser': 7.27.7 From a1f7fda00b8da425b4ddad436d2bde15ece0e972 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 14 Aug 2025 10:11:39 +1000 Subject: [PATCH 4/8] Refactor --- .../app/routes/mdx-glob.$post/posts/posts.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts index e8e32d50a8..ec1fdcdc6b 100644 --- a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/posts/posts.ts @@ -19,8 +19,8 @@ async function resolvePosts(): Promise<{ >; return Object.fromEntries( - Object.entries(rawPosts).map(([path, loadPost]) => { - const slug = path.split("/").pop()!.replace(".mdx", ""); + Object.entries(rawPosts).map(([importPath, loadPost]) => { + const slug = importPath.split("/").pop()!.replace(".mdx", ""); return [ slug, @@ -38,7 +38,7 @@ async function resolvePosts(): Promise<{ } else { const prettyPath = nodePath.relative( process.cwd(), - nodePath.resolve(import.meta.dirname, path), + nodePath.resolve(import.meta.dirname, importPath), ); console.error( `Invalid frontmatter for ${prettyPath}: Missing title`, From cf2b92a1019bbcab9a247e4516f2cdbeadbdc11f Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 14 Aug 2025 10:15:35 +1000 Subject: [PATCH 5/8] Add post title to meta --- .../rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx index 185d07ab89..3c53c3456e 100644 --- a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx @@ -8,7 +8,11 @@ export async function loader({ params }: Route.LoaderArgs) { throw new Response("Not Found", { status: 404, statusText: "Not Found" }); } - return { postElement: }; + return { title: post.title, postElement: }; +} + +export function meta({ loaderData }: Route.ComponentProps) { + return [{ title: loaderData.title }]; } export function ServerComponent({ loaderData }: Route.ComponentProps) { From c8cbfc1a01f4880c3880169dbc9f762492938905 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 14 Aug 2025 10:20:54 +1000 Subject: [PATCH 6/8] Refactor --- .../rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx index 3c53c3456e..20631fa68a 100644 --- a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx @@ -8,7 +8,10 @@ export async function loader({ params }: Route.LoaderArgs) { throw new Response("Not Found", { status: 404, statusText: "Not Found" }); } - return { title: post.title, postElement: }; + return { + title: post.title, + element: , + }; } export function meta({ loaderData }: Route.ComponentProps) { @@ -16,5 +19,5 @@ export function meta({ loaderData }: Route.ComponentProps) { } export function ServerComponent({ loaderData }: Route.ComponentProps) { - return loaderData.postElement; + return loaderData.element; } From 83186f9d7eaa1e35f12663f6771097ca91caefb1 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 14 Aug 2025 12:16:00 +1000 Subject: [PATCH 7/8] Fix meta type annotation --- .../rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx index 20631fa68a..a012ab0692 100644 --- a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx @@ -14,8 +14,8 @@ export async function loader({ params }: Route.LoaderArgs) { }; } -export function meta({ loaderData }: Route.ComponentProps) { - return [{ title: loaderData.title }]; +export function meta({ loaderData }: Route.MetaArgs) { + return [{ title: loaderData!.title }]; } export function ServerComponent({ loaderData }: Route.ComponentProps) { From 58917b6503be320eb5d392c19e7fa7722ecead33 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 14 Aug 2025 13:36:07 +1000 Subject: [PATCH 8/8] Remove non-null assertion --- .../rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx index a012ab0692..0ad3c2d7c0 100644 --- a/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx +++ b/playground/rsc-vite-framework/app/routes/mdx-glob.$post/route.tsx @@ -15,7 +15,7 @@ export async function loader({ params }: Route.LoaderArgs) { } export function meta({ loaderData }: Route.MetaArgs) { - return [{ title: loaderData!.title }]; + return [{ title: loaderData.title }]; } export function ServerComponent({ loaderData }: Route.ComponentProps) {