WIP: Architecture Refactor, add stub files and working blog post wrapped by AppShell
This commit is contained in:
parent
c34f11de00
commit
3abd97702d
31
Dockerfile
31
Dockerfile
@ -1,31 +0,0 @@
|
|||||||
# use the official Bun image
|
|
||||||
# see all versions at https://hub.docker.com/r/oven/bun/tags
|
|
||||||
FROM oven/bun:1.0.25-alpine as base
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
# install dependencies into temp directory
|
|
||||||
# this will cache them and speed up future builds
|
|
||||||
FROM base AS install
|
|
||||||
RUN mkdir -p /temp/dev
|
|
||||||
COPY package.json bun.lockb /temp/dev/
|
|
||||||
RUN cd /temp/dev && bun install --frozen-lockfile
|
|
||||||
|
|
||||||
# install with --production (exclude devDependencies)
|
|
||||||
RUN mkdir -p /temp/prod
|
|
||||||
COPY package.json bun.lockb /temp/prod/
|
|
||||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
|
||||||
|
|
||||||
# copy node_modules from temp directory
|
|
||||||
# then copy all (non-ignored) project files into the image
|
|
||||||
FROM base AS release
|
|
||||||
COPY --from=install /temp/dev/node_modules node_modules
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# [optional] tests & build
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
RUN bun test
|
|
||||||
|
|
||||||
# run the app
|
|
||||||
USER bun
|
|
||||||
EXPOSE 3000/tcp
|
|
||||||
ENTRYPOINT [ "bun", "run", "./src/index.ts" ]
|
|
||||||
77
index.tsx
77
index.tsx
@ -1,49 +1,38 @@
|
|||||||
// import { Elysia } from "elysia";
|
import AppShellDemo from "./temp/appshell.html";
|
||||||
// import { html } from "@elysiajs/html";
|
import { AppShell } from "./src/frontend/AppShell";
|
||||||
// import { staticPlugin } from "@elysiajs/static";
|
|
||||||
|
|
||||||
// import { AppShell } from "./src/frontend/AppShell";
|
async function blogPosts() {
|
||||||
// import { app } from "./src/backend";
|
const glob = new Bun.Glob("**/*.md");
|
||||||
|
const blogPosts: Record<string, any> = {}
|
||||||
// const index = new Elysia()
|
for await (const file of glob.scan("./content")) {
|
||||||
// .use(html())
|
|
||||||
// .onRequest(({ request }) => {
|
|
||||||
// console.log(`Request ${request.method} ${request.url}`);
|
|
||||||
// })
|
|
||||||
// .onAfterHandle(({ request, responseValue }) => {
|
|
||||||
// if (request.headers.get("shell-loaded") === "true") {
|
|
||||||
// return responseValue; // Return the <main> element if the AppShell has already been loaded
|
|
||||||
// }
|
|
||||||
// return AppShell(responseValue); // Return the <main> element wrapped by the AppShell
|
|
||||||
// })
|
|
||||||
// .use(staticPlugin({
|
|
||||||
// assets: './src/public',
|
|
||||||
// prefix: '/public'
|
|
||||||
// }))
|
|
||||||
// .use(app)
|
|
||||||
// .listen(3000);
|
|
||||||
|
|
||||||
// console.log(
|
|
||||||
// `🦊 Elysia is running at ${index.server?.hostname}:${index.server?.port}`
|
|
||||||
// );
|
|
||||||
|
|
||||||
|
|
||||||
import { serve } from "bun";
|
|
||||||
import AppShell from "./temp/appshell.html"
|
|
||||||
|
|
||||||
// Dynamically import all markdown files
|
|
||||||
const glob = new Bun.Glob("**/*.md");
|
|
||||||
const routes: Record<string, any> = {
|
|
||||||
'/': AppShell,
|
|
||||||
};
|
|
||||||
|
|
||||||
for await (const file of glob.scan("./content")) {
|
|
||||||
const post = await import(`./content/${file}`, { with: { type: "html" } });
|
const post = await import(`./content/${file}`, { with: { type: "html" } });
|
||||||
const route = `/${file.replace(/\.md$/, '')}`;
|
const route = `/${file.replace(/\.md$/, "")}`;
|
||||||
routes[route] = post.default;
|
|
||||||
|
blogPosts[route] = post.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(blogPosts).map((route) => {
|
||||||
|
console.info(route);
|
||||||
|
});
|
||||||
|
return blogPosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
serve({
|
Bun.serve({
|
||||||
routes,
|
development: {
|
||||||
development: true,
|
hmr: 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
|
||||||
import { Elysia, NotFoundError } from "elysia";
|
|
||||||
|
|
||||||
import { Home } from "../frontend/home";
|
|
||||||
import { Blog } from "../frontend/blog";
|
|
||||||
import { NotFound } from "../frontend/not-found";
|
|
||||||
|
|
||||||
|
|
||||||
export const app = new Elysia()
|
|
||||||
.onError(({ error }) => {
|
|
||||||
if(error instanceof NotFoundError) {
|
|
||||||
return <NotFound />;
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
})
|
|
||||||
.get("/", () => { return <Home /> })
|
|
||||||
.get("/:path", ({ path }) => {
|
|
||||||
if(path === "/blog") {
|
|
||||||
return <Blog />;
|
|
||||||
}
|
|
||||||
throw new NotFoundError();
|
|
||||||
})
|
|
||||||
@ -1,7 +1,10 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import React from 'react';
|
||||||
|
import styles from '../public/styles.css' with { type: "text" };
|
||||||
|
import headScript from '../public/head.js' with { type: "text" };
|
||||||
|
import onLoadScript from '../public/onLoad.js' with { type: "text" };
|
||||||
|
|
||||||
// Helper: Minify CSS using simple but effective regex
|
// Helper: Minify CSS using simple but effective regex
|
||||||
async function minifyCSS(css: string): Promise<string> {
|
function minifyCSS(css: string): string {
|
||||||
return css
|
return css
|
||||||
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
|
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
|
||||||
.replace(/\s+/g, ' ') // Collapse whitespace
|
.replace(/\s+/g, ' ') // Collapse whitespace
|
||||||
@ -10,7 +13,7 @@ async function minifyCSS(css: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Minify JS/TS using Bun.Transpiler
|
// Helper: Minify JS/TS using Bun.Transpiler
|
||||||
async function minifyJS(code: string): Promise<string> {
|
function minifyJS(code: string): string {
|
||||||
const transpiler = new Bun.Transpiler({
|
const transpiler = new Bun.Transpiler({
|
||||||
loader: 'ts',
|
loader: 'ts',
|
||||||
minifyWhitespace: true,
|
minifyWhitespace: true,
|
||||||
@ -19,41 +22,21 @@ async function minifyJS(code: string): Promise<string> {
|
|||||||
return transpiler.transformSync(code);
|
return transpiler.transformSync(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and minify files at module load time (runs once)
|
export function AppShell(props: { post: any }) {
|
||||||
console.log('[AppShell] Loading and minifying assets...');
|
|
||||||
|
|
||||||
const rawStyles = await Bun.file("./src/public/styles.css").text();
|
|
||||||
const rawHeadScript = await Bun.file("./src/public/head.ts").text();
|
|
||||||
const rawOnLoadScript = await Bun.file("./src/public/onLoad.ts").text();
|
|
||||||
|
|
||||||
// Minify all assets
|
|
||||||
const styles = await minifyCSS(rawStyles);
|
|
||||||
const headScript = await minifyJS(rawHeadScript);
|
|
||||||
const onLoadScript = await minifyJS(rawOnLoadScript);
|
|
||||||
|
|
||||||
console.log('[AppShell] Assets minified and cached in memory');
|
|
||||||
console.log(` CSS: ${rawStyles.length} → ${styles.length} bytes (-${Math.round((1 - styles.length / rawStyles.length) * 100)}%)`);
|
|
||||||
console.log(` head.ts: ${rawHeadScript.length} → ${headScript.length} bytes (-${Math.round((1 - headScript.length / rawHeadScript.length) * 100)}%)`);
|
|
||||||
console.log(` onLoad.ts: ${rawOnLoadScript.length} → ${onLoadScript.length} bytes (-${Math.round((1 - onLoadScript.length / rawOnLoadScript.length) * 100)}%)`);
|
|
||||||
|
|
||||||
export function AppShell(responseValue: any) {
|
|
||||||
return (
|
return (
|
||||||
<html>
|
<html className={styles.root}>
|
||||||
<head>
|
<head>
|
||||||
<title>Caleb's Blog</title>
|
<title>Caleb's Blog</title>
|
||||||
<style>
|
<style>{minifyCSS(styles)}</style>
|
||||||
{styles}
|
|
||||||
</style>
|
|
||||||
<script>
|
<script>
|
||||||
{headScript}
|
{minifyJS(headScript)}
|
||||||
</script>
|
</script>
|
||||||
<script defer>
|
<script defer>
|
||||||
{onLoadScript}
|
{minifyJS(onLoadScript)}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{responseValue}
|
<div dangerouslySetInnerHTML={{ __html: props.post }} />
|
||||||
<aside>
|
<aside>
|
||||||
<h3>About Me</h3>
|
<h3>About Me</h3>
|
||||||
<p>I'm a software engineer</p>
|
<p>I'm a software engineer</p>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import React from 'react';
|
||||||
|
|
||||||
export function Blog() {
|
export function Blog() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
0
src/frontend/components/post-archive.tsx
Normal file
0
src/frontend/components/post-archive.tsx
Normal file
0
src/frontend/components/profile-badge.tsx
Normal file
0
src/frontend/components/profile-badge.tsx
Normal file
0
src/frontend/components/tag-picker.tsx
Normal file
0
src/frontend/components/tag-picker.tsx
Normal file
13
src/frontend/components/theme-picker.tsx
Normal file
13
src/frontend/components/theme-picker.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function ThemePicker() {
|
||||||
|
|
||||||
|
const randomNumber = Math.random();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Sub Component!</h1>
|
||||||
|
<p>Some text here... maybe? with hot reloading! {randomNumber}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import React from 'react';
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import React from 'react';
|
||||||
|
|
||||||
export function NotFound() {
|
export function NotFound() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
12
src/frontend/pages/blog.tsx
Normal file
12
src/frontend/pages/blog.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ThemePicker } from "../components/theme-picker";
|
||||||
|
|
||||||
|
export function Blog() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<h1>Blog</h1>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<ThemePicker />
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
10
src/frontend/pages/home.tsx
Normal file
10
src/frontend/pages/home.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function Home() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<h1>Home!</h1>
|
||||||
|
<a href="/blog">Blog</a>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
src/frontend/pages/not-found.tsx
Normal file
9
src/frontend/pages/not-found.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function NotFound() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<h1>404 Not Found</h1>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
1
src/public/htmx.min.js
vendored
1
src/public/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user