Mount JSX server side templating for blog posts. Send AppShell conditionally. Maintain support for HMR via HTMLbundles using Bun's native fullstack dev server under an /hmr path. This is only mounted in development and is supported by the onImport Bun plugin. Add DB creation on startup and load pages based on those records.
137 lines
3.9 KiB
TypeScript
137 lines
3.9 KiB
TypeScript
import React from "react";
|
|
// Database connection is now handled by the centralized db module
|
|
import { renderToString } from "react-dom/server";
|
|
import { AppShell } from "./src/frontend/AppShell";
|
|
import { Home } from "./src/frontend/pages/home";
|
|
import { NotFound } from "./src/frontend/pages/not-found";
|
|
import demo from "./temp/appshell.html";
|
|
import { Post } from "./src/frontend/pages/post";
|
|
import { getPostWithTags } from "./src/db/index";
|
|
|
|
async function blogPosts(hmr: boolean) {
|
|
const glob = new Bun.Glob("**/*.md");
|
|
const blogPosts: Record<string, any> = {};
|
|
for await (const file of glob.scan("./content")) {
|
|
const post = await import(`./content/${file}`, { with: { type: "html" } });
|
|
const route = `/${file.replace(/\.md$/, "")}`;
|
|
|
|
if (hmr) {
|
|
// Use Bun Importer plugin for hot reloading in the browser
|
|
blogPosts[`/hmr${route}`] = post.default;
|
|
} else {
|
|
// Use the Database for sending just the HTML or the HTML and AppShell
|
|
blogPosts[route] = (req: Request) => {
|
|
const path = new URL(req.url).pathname;
|
|
const post = getPostWithTags(path);
|
|
if (!post)
|
|
return new Response(renderToString(<NotFound />), {
|
|
status: 404,
|
|
headers: { "Content-Type": "text/html" },
|
|
});
|
|
|
|
const data = {
|
|
title: post.title,
|
|
summary: post.summary,
|
|
date: new Date(post.date),
|
|
readingTime: post.reading_time,
|
|
tags: post.tags || [],
|
|
};
|
|
|
|
// AppShell is already loaded, just send the <main> content
|
|
if (req.headers.get("shell-loaded") === "true") {
|
|
return new Response(
|
|
renderToString(<Post meta={data} children={post.content} />),
|
|
{
|
|
headers: {
|
|
"Content-Type": "text/html",
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
// AppShell is not loaded, send the <AppShell> with the <main> content inside
|
|
return new Response(
|
|
renderToString(
|
|
<AppShell>
|
|
<Post meta={data} children={post.content} />
|
|
</AppShell>,
|
|
),
|
|
{
|
|
headers: {
|
|
"Content-Type": "text/html",
|
|
},
|
|
},
|
|
);
|
|
};
|
|
}
|
|
}
|
|
|
|
Object.keys(blogPosts).map((route) => {
|
|
console.info(route);
|
|
});
|
|
return blogPosts;
|
|
}
|
|
|
|
Bun.serve({
|
|
development: {
|
|
hmr: true,
|
|
console: true,
|
|
},
|
|
|
|
routes: {
|
|
// standard mounting of blog posts
|
|
...(await blogPosts(false)),
|
|
|
|
// hot module replacement in development mode
|
|
...(process.env.NODE_ENV === "development" ? (await blogPosts(true)) : []),
|
|
|
|
// Home page
|
|
"/": (req: Request) => {
|
|
// Extract URL parameters from the request to pass to the component
|
|
const url = new URL(req.url);
|
|
const searchParams = Object.fromEntries(url.searchParams.entries());
|
|
|
|
if (req.headers.get("shell-loaded") === "true") {
|
|
return new Response(renderToString(<Home searchParams={searchParams} />), {
|
|
headers: {
|
|
"Content-Type": "text/html",
|
|
},
|
|
});
|
|
}
|
|
|
|
return new Response(
|
|
renderToString(
|
|
<AppShell>
|
|
<Home searchParams={searchParams} />
|
|
</AppShell>,
|
|
),
|
|
{
|
|
headers: {
|
|
"Content-Type": "text/html",
|
|
},
|
|
},
|
|
);
|
|
},
|
|
"/target": demo,
|
|
"/profile-picture.webp": () => {
|
|
return new Response(Bun.file("./src/public/profile-picture.webp"), {
|
|
headers: {
|
|
"Content-Type": "image/webp",
|
|
},
|
|
});
|
|
},
|
|
"/*": (req) => {
|
|
if(req.headers.get("shell-loaded") === "true") {
|
|
return new Response(renderToString(<NotFound />), {
|
|
status: 404,
|
|
headers: { "Content-Type": "text/html" },
|
|
});
|
|
}
|
|
return new Response(renderToString(<AppShell><NotFound /></AppShell>), {
|
|
status: 404,
|
|
headers: { "Content-Type": "text/html" },
|
|
});
|
|
}
|
|
},
|
|
});
|