import type { BunPlugin } from 'bun'; import React from 'react'; import { renderToString } from "react-dom/server"; import matter from 'gray-matter'; import { marked } from 'marked'; import { addToDatabase } from "../src/db/index"; import { AppShell } from "../src/frontend/AppShell"; import { Post } from "../src/frontend/pages/post"; // TODO: Add better type handling for if Markdown parsing fails const markdownLoader: BunPlugin = { name: 'markdown-loader', setup(build) { // Plugin implementation build.onLoad({filter: /\.md$/}, async args => { console.log("Loading markdown file:", args.path); const {data, content } = matter(await Bun.file(args.path).text()); // Remove the title from content if it matches the frontmatter title to avoid duplicate H1s let processedContent = content; if (data.title) { const titleHeadingRegex = new RegExp(`^#\\s+${data.title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'm'); processedContent = content.replace(titleHeadingRegex, '').trim(); } const bodyHtml = await marked.parse(processedContent); // AppShell is required here for rendering. If used at route level // Bun will only see an htmlBundle and fail to load anything // Validate required fields if (!data.title || !data.date) { throw new Error(`Markdown files must include title and date in frontmatter: ${args.path}`); } const meta = { title: data.title, date: new Date(data.date), readingTime: data.readingTime || `${Math.ceil(content.split(/\s+/).length / 200)} min read` }; const renderedHtml = renderToString(); addToDatabase(args.path, meta, bodyHtml); // Load the post to the database for dynamic querying // JSX Approach return { contents: renderedHtml, loader: 'html', }; }); }, }; export default markdownLoader;