Skip to content

Commit

Permalink
feat(#3): mdx rendering (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
yoannfleurydev committed Jan 15, 2024
1 parent bec168d commit a1d5a2e
Show file tree
Hide file tree
Showing 7 changed files with 907 additions and 28 deletions.
4 changes: 3 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"lint": "eslint . --max-warnings 0"
},
"dependencies": {
"typed-mdx": "workspace:*",
"@mdx-js/react": "3.0.0",
"@mdx-js/mdx": "3.0.0",
"next": "14.0.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"typed-mdx": "workspace:*",
"zod": "3.22.4"
},
"devDependencies": {
Expand Down
9 changes: 6 additions & 3 deletions apps/web/src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Renderer } from "@/app/blog/[slug]/renderer";
import collections from "@/content/collections";

export default async function BlogPostPage({
Expand All @@ -9,8 +10,10 @@ export default async function BlogPostPage({
const author = await collections.author.getBySlug(blogPost.author);

return (
<>
{blogPost.title} by {author.name}
</>
<article>
<h2>{blogPost.title}</h2>
<small>by {author.name}</small>
<Renderer code={blogPost.body.code} />
</article>
);
}
24 changes: 24 additions & 0 deletions apps/web/src/app/blog/[slug]/renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use client";

import { Fragment, useEffect, useState } from "react";
import { run } from "@mdx-js/mdx";
import * as runtime from "react/jsx-runtime";
import { MDXModule } from "node_modules/@mdx-js/mdx/lib/run";

type RendererProps = { code: string };

export const Renderer = ({ code }: RendererProps) => {
const [mdxModule, setMdxModule] = useState<MDXModule>();
const Content = mdxModule ? mdxModule.default : Fragment;

useEffect(
function () {
(async function () {
setMdxModule(await run(code, { ...runtime, baseUrl: undefined }));
})();
},
[code]
);

return <Content />;
};
6 changes: 3 additions & 3 deletions packages/typed-mdx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ other alternative to Astro Content Collection for Next.js.
## Installation

```bash
npm install typed-mdx zod
yarn add typed-mdx zod
pnpm add typed-mdx zod
npm install typed-mdx zod @mdx-js/react
yarn add typed-mdx zod @mdx-js/react
pnpm add typed-mdx zod @mdx-js/react
```

## Usage
Expand Down
8 changes: 5 additions & 3 deletions packages/typed-mdx/package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
{
"name": "typed-mdx",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"dev": "pnpm build -- --watch",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
"build": "tsup src/index.ts --format cjs,esm --dts",
"test": "echo 'Add test script here'",
"lint": "echo 'Add lint script here'"
},
"dependencies": {
"zod": "3.22.4",
"gray-matter": "4.0.3"
"gray-matter": "4.0.3",
"@mdx-js/mdx": "3.0.0",
"zod": "3.22.4"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
Expand Down
43 changes: 37 additions & 6 deletions packages/typed-mdx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import z from "zod";
import matter from "gray-matter";
import { compile } from "@mdx-js/mdx";

const CONTENT_FOLDER = "src/content";

Expand Down Expand Up @@ -53,10 +54,28 @@ async function parseMdxFile<Z extends z.Schema>(
return frontmatter.data;
}

async function getMDXContent({ path }: { path: string }) {
try {
const fileContent = await fs.readFile(path);
const mdxContent = await compile(fileContent, {
outputFormat: "function-body",
});

return {
raw: fileContent.toString(),
code: mdxContent.toString(),
};
} catch {
console.error(`Can't read file ${path}`);
return {};
}
}

const metadataSchema = z.object({
metadata: z.object({
slug: z.string(),
}),
body: z.object({ raw: z.string(), code: z.string() }),
});

const MDX_ENTENSION = ".mdx";
Expand Down Expand Up @@ -84,20 +103,24 @@ async function getAll<Z extends z.Schema>({
schema
);

const body = await getMDXContent({
path: path.resolve(folderPath, mdxFileName),
});

const metadata = {
slug: mdxFileName.substring(
0,
mdxFileName.length - MDX_ENTENSION.length
),
};

return { metadata, ...parsedFrontmatter };
return { metadata, body, ...parsedFrontmatter };
})
);

return z
.array(schema.merge(metadataSchema))
.parse(data.filter(Boolean)) as any; // FIX THIS
.parse(data.filter(Boolean)) as any; // TODO FIX THIS ANY
}

async function getBySlug<Z extends z.Schema>({
Expand All @@ -119,11 +142,19 @@ async function getBySlug<Z extends z.Schema>({
throw new Error(`File ${filePath} not found`);
}

const data = await parseMdxFile(filePath, schema);
const parsedFrontmatter = await parseMdxFile(filePath, schema);

const body = await getMDXContent({
path: path.resolve(filePath),
});

const metadata = {
slug,
};

const data = { metadata, body, ...parsedFrontmatter };

// Trick to make zod infer the schema, else it doesn't infer if we do not put
// the z.object({}) before. Maybe the generic is not small enough ?
return schema.parse(data);
return schema.merge(metadataSchema).parse(data) as any; // TODO FIX THIS ANY
}

export function defineCollection<Z extends z.Schema>(options: {
Expand Down
Loading

0 comments on commit a1d5a2e

Please sign in to comment.