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:
128
index.tsx
128
index.tsx
@@ -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" },
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user