Complete WIP: Architecture refactor.

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.
This commit is contained in:
2026-01-08 05:13:48 -08:00
parent 3abd97702d
commit f46f4667a1
32 changed files with 2779 additions and 353 deletions

128
index.tsx
View File

@@ -1,14 +1,69 @@
import AppShellDemo from "./temp/appshell.html";
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() {
async function blogPosts(hmr: boolean) {
const glob = new Bun.Glob("**/*.md");
const blogPosts: Record<string, any> = {}
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$/, "")}`;
blogPosts[route] = post.default;
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) => {
@@ -20,19 +75,62 @@ async function blogPosts() {
Bun.serve({
development: {
hmr: true,
console: true
console: true,
},
routes: {
"/": AppShellDemo,
... await blogPosts(),
"/content/*": {
async GET(req: Request) {
// Having trouble using Bun Bundler alongside a custom route handler to send
// different content depending on the request headers, will use /content subpath instead
// (unless I can figure it out)
return new Response("This will send the blog post content without the app shell")
// 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" },
});
}
}
})
},
});