Replace Elysia with bun.serve for hot reloading and static serving

This commit is contained in:
Caleb Braaten 2025-10-22 19:56:38 -07:00
parent b136c6e63a
commit cd88f570a0
13 changed files with 522 additions and 27 deletions

View File

@ -7,6 +7,8 @@
"@elysiajs/html": "1.4.0",
"@elysiajs/static": "1.4.4",
"elysia": "^1.4.11",
"gray-matter": "^4.0.3",
"marked": "^16.4.1",
},
"devDependencies": {
"@types/bun": "^1.3.0",
@ -40,6 +42,8 @@
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
@ -58,8 +62,12 @@
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="],
"extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
@ -70,16 +78,32 @@
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
"js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
"marked": ["marked@16.4.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="],
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
"token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="],

View File

@ -0,0 +1,19 @@
import type {BunPlugin} from 'bun';
import { marked } from 'marked';
const markdownLoader: BunPlugin = {
name: 'markdown-loader',
setup(build) {
// Plugin implementation
build.onLoad({filter: /\.md$/}, async args => {
const text = await Bun.file(args.path).text();
const html = marked.parse(text);
return {
contents: `export default ${html};`,
loader: 'html',
};
});
},
};
export default markdownLoader;

2
bunfig.toml Normal file
View File

@ -0,0 +1,2 @@
[serve.static]
plugins = ["./bun_plugins/markdown-loader.ts"]

34
content/.template.md Normal file
View File

@ -0,0 +1,34 @@
---
title: Your Post Title
date: 2025-10-21
tags: [Web Development, TypeScript]
excerpt: A brief summary of your post (2-3 sentences). This will appear in post listings and search results.
draft: false
---
# Your Post Title
Your content here. You can use standard markdown syntax:
## Section Heading
Write your paragraphs with proper spacing.
### Subsection
- Bullet points
- Another point
- And another
**Bold text** and *italic text* are supported.
```typescript
// Code blocks work too
const example = "Hello World";
console.log(example);
```
> Blockquotes for important callouts or quotes.
This is just a template - delete this file or ignore it when writing your actual posts.

View File

@ -0,0 +1,93 @@
---
title: 2024 Year in Review
date: 2024-12-28
tags: [Career, Productivity]
excerpt: Reflecting on 2024 - the projects I built, lessons I learned, and goals for 2025. A year of growth, challenges, and accomplishments.
draft: false
---
# 2024 Year in Review!
As 2024 comes to a close, I wanted to take some time to reflect on the year. It's been a year of significant growth, both professionally and personally.
## Professional Highlights
### Projects
This year, I worked on several exciting projects:
**E-commerce Platform**: Led the frontend architecture for a new e-commerce platform serving 100k+ daily users. We achieved a 40% improvement in page load times through careful optimization.
**Design System**: Built a comprehensive design system from scratch, complete with documentation and Storybook. This is now used across 5 different products.
**Open Source**: Made my first significant open source contributions! Contributed to several Bun-related projects and created a few small utilities that others found useful.
### Skills Developed
- **Performance Optimization**: Learned a ton about web performance, Core Web Vitals, and how to actually make sites fast
- **System Design**: Started thinking more about architecture and scalability
- **TypeScript**: Became much more proficient with advanced TypeScript patterns
- **Testing**: Finally built a solid testing practice
## Personal Growth
### Writing
Started this blog! Writing has helped me:
- Clarify my thinking
- Learn more deeply
- Connect with others in the community
### Work-Life Balance
Made a conscious effort to improve work-life balance:
- Implemented a hard stop at 6 PM
- Started exercising regularly (3x per week)
- Picked up reading again (finished 12 books!)
## Challenges
Not everything was smooth sailing:
**Burnout**: Hit a rough patch in Q2 where I was working too much. Learned the importance of rest and boundaries.
**Imposter Syndrome**: Still struggles with this occasionally, but getting better at recognizing it and pushing through.
**Saying No**: Learned that saying no to some opportunities is necessary to say yes to the right ones.
## Lessons Learned
1. **Quality > Quantity**: Better to do fewer things well than many things poorly
2. **Ask for Help**: Nobody knows everything, and asking for help is a strength
3. **Document Everything**: Future you will thank present you
4. **Take Breaks**: Rest isn't laziness - it's necessary for sustained performance
5. **Community Matters**: Connecting with other developers has been invaluable
## Goals for 2025
Looking ahead to 2025:
### Professional
- Contribute to a major open source project
- Speak at a local meetup or conference
- Learn Rust (for real this time)
- Build and launch a side project
### Personal
- Write 24 blog posts (2 per month)
- Read 20 books
- Maintain exercise routine
- Learn to cook 10 new recipes
### Learning
- Deep dive into system design
- Master web performance
- Learn more about databases
- Get better at writing
## Thank You
Thanks to everyone who supported me this year - colleagues, friends, family, and the online tech community. Looking forward to 2025!
What were your highlights from 2024? What are you looking forward to in 2025?

View File

@ -0,0 +1,108 @@
---
title: 5 TypeScript Tips I Wish I Knew Earlier
date: 2025-09-18
tags: [TypeScript, JavaScript, Productivity]
excerpt: Five practical TypeScript tips that will make your code more type-safe and your development experience smoother. From utility types to const assertions.
draft: false
---
# 5 TypeScript Tips I Wish I Knew Earlier
TypeScript is an amazing tool, but it takes time to learn all its features. Here are five tips that significantly improved my TypeScript development.
## 1. Use `satisfies` for Type Checking
The `satisfies` keyword (added in TS 4.9) lets you validate that a value matches a type without widening its type:
```typescript
type RGB = { r: number; g: number; b: number };
// Before: Type is widened to RGB
const color: RGB = { r: 255, g: 0, b: 0 };
// After: Type is preserved as literal values
const color = { r: 255, g: 0, b: 0 } satisfies RGB;
```
This is especially useful when you want both type safety and precise types.
## 2. Const Assertions
Adding `as const` to an object or array makes all properties readonly and infers literal types:
```typescript
// Regular array: type is string[]
const fruits = ['apple', 'banana'];
// With const assertion: type is readonly ['apple', 'banana']
const fruits = ['apple', 'banana'] as const;
```
This is perfect for configuration objects and enum-like values.
## 3. Utility Type: `Awaited<T>`
Need to get the resolved type of a Promise? Use `Awaited`:
```typescript
async function fetchUser() {
return { id: 1, name: 'Alice' };
}
// Gets the return type without the Promise wrapper
type User = Awaited<ReturnType<typeof fetchUser>>;
// User is { id: number; name: string }
```
## 4. Template Literal Types
Create types based on string patterns:
```typescript
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// Result: 'onClick' | 'onFocus' | 'onBlur'
```
This is incredibly powerful for creating type-safe APIs.
## 5. Discriminated Unions
Use a common property to narrow union types:
```typescript
type Success = { status: 'success'; data: string };
type Error = { status: 'error'; error: string };
type Result = Success | Error;
function handle(result: Result) {
if (result.status === 'success') {
// TypeScript knows this is Success
console.log(result.data);
} else {
// TypeScript knows this is Error
console.log(result.error);
}
}
```
This pattern eliminates entire classes of runtime errors.
## Bonus: Non-Null Assertion Operator
While I generally avoid the `!` operator, it's useful when you know better than TypeScript:
```typescript
// When you're certain the element exists
const button = document.getElementById('submit')!;
button.click();
```
Use sparingly and only when you're absolutely sure.
## Wrapping Up
These tips have made my TypeScript code more robust and easier to maintain. The key is to leverage TypeScript's type system to catch errors at compile time rather than runtime.
What are your favorite TypeScript features? Let me know!

View File

@ -0,0 +1,34 @@
---
title: Your Post Title
date: 2025-10-21
tags: [Web Development, TypeScript]
excerpt: A brief summary of your post (2-3 sentences). This will appear in post listings and search results.
draft: false
---
# Your Post Title
Your content here. You can use standard markdown syntax:
## Section Heading
Write your paragraphs with proper spacing.
### Subsection
- Bullet points
- Another point
- And another
**Bold text** and *italic text* are supported.
```typescript
// Code blocks work too
const example = "Hello World";
console.log(example);
```
> Blockquotes for important callouts or quotes.
This is just a template - delete this file or ignore it when writing your actual posts.

View File

@ -0,0 +1,103 @@
---
title: Building a Modern Blog with Bun and TypeScript
date: 2025-10-20
tags: [Web Development, TypeScript, JavaScript]
excerpt: A deep dive into building a performant, modern blog using Bun's runtime, TypeScript, and server-side rendering. Learn about the architecture decisions and tradeoffs.
draft: false
---
# Building a Modern Blog with Bun and TypeScript
When I set out to build this blog, I had a few key requirements in mind: fast page loads, minimal JavaScript shipped to the client, and a great developer experience. Here's how I achieved all three.
## Why Bun?
Bun is an all-in-one JavaScript runtime that's significantly faster than Node.js for many operations. It includes:
- A blazing-fast JavaScript/TypeScript runtime
- Built-in bundler
- Native TypeScript support
- Package manager
- Test runner
For this blog, the combination of native TypeScript support and the built-in bundler made Bun an obvious choice.
## Architecture Overview
The blog uses a hybrid approach:
### Server-Side Rendering
All HTML is generated on the server using JSX as a templating language. This means:
- **Fast initial page loads** - No waiting for JavaScript to download and execute
- **SEO friendly** - Search engines see fully rendered HTML
- **Works without JavaScript** - Core functionality doesn't depend on client-side JS
### AppShell Pattern
The blog implements an "AppShell" pattern where:
1. First visit loads the full page with AppShell (sidebar, header, etc.)
2. Navigation replaces only the `<main>` content
3. Subsequent requests send a custom header to indicate AppShell is already loaded
4. Server returns just the content, not the full shell
This gives us SPA-like navigation speed with SSR benefits.
## Markdown Processing
Blog posts are written in Markdown and processed at build time:
```typescript
// Simplified example
import { marked } from 'marked';
const html = marked.parse(markdownContent);
```
The plugin:
- Scans the content directory
- Parses frontmatter (title, date, tags, etc.)
- Converts markdown to HTML
- Stores everything in SQLite for fast queries
## Performance Results
The result? Page loads under 128KB including:
- All HTML
- All CSS (inlined)
- Minimal JavaScript for interactivity
First contentful paint happens in under 100ms on a fast connection.
## Developer Experience
With Bun's `--watch` flag, the entire development workflow is seamless:
```bash
bun run --watch ./index.tsx
```
This watches for changes and hot-reloads the server. Combined with the markdown plugin, changing a blog post immediately reflects in the browser.
## Lessons Learned
### What Worked Well
- **JSX for templating** - Familiar, type-safe, and no new syntax to learn
- **SQLite for search** - Fast, embedded, and perfect for a small blog
- **Bun's speed** - Development is incredibly fast
### What Could Be Better
- **Hot reloading** - Would love client-side HMR for styles
- **Build step** - Currently all processing happens at runtime
- **TypeScript types** - Virtual modules need proper type definitions
## Conclusion
Building with Bun has been a great experience. The performance is excellent, and the developer experience is top-notch. If you're building a new project and want to try something modern, I highly recommend giving Bun a shot.
The code for this blog is open source - check it out on GitHub!

View File

@ -0,0 +1,51 @@
---
title: Welcome to My Blog
date: 2025-10-15
tags: [Career, Web Development]
excerpt: Starting a new blog to share my thoughts on web development, programming, and building great software. Here's why I decided to start writing.
draft: false
---
# Welcome to My Blog
I'm excited to finally launch my personal blog! After years of thinking about it, I've decided it's time to start sharing my experiences, learnings, and thoughts about web development and software engineering.
## Why Start a Blog?
There are a few reasons I decided to finally take the plunge:
**Learning in Public**: Writing about what I learn helps solidify my understanding. Teaching is one of the best ways to learn, and writing blog posts forces me to really understand a topic deeply.
**Building a Knowledge Base**: I often solve problems and then forget the solution months later. This blog will serve as my personal reference guide for future me.
**Contributing to the Community**: The developer community has given me so much through blog posts, tutorials, and open source. This is my way of giving back.
## What to Expect
I plan to write about:
- Web development best practices
- TypeScript and JavaScript tips
- Architecture and design patterns
- Career advice and lessons learned
- Tools and productivity
## The Tech Stack
This blog itself is built with some fun technologies:
- **Bun** - The all-in-one JavaScript runtime
- **TypeScript** - For type safety
- **Elysia** - Fast web framework for Bun
- **SQLite** - For blog post search and metadata
I'll be writing more about the technical implementation in future posts.
## Let's Connect
If you enjoy the content, feel free to reach out! You can find me on Twitter, GitHub, and LinkedIn (links in the sidebar).
Thanks for reading, and I hope you find something useful here!
Test change at Wed Oct 22 06:20:32 PDT 2025
<!-- test change -->

View File

@ -1,28 +1,49 @@
import { Elysia } from "elysia";
import { html } from "@elysiajs/html";
import { staticPlugin } from "@elysiajs/static";
// import { Elysia } from "elysia";
// import { html } from "@elysiajs/html";
// import { staticPlugin } from "@elysiajs/static";
import { AppShell } from "./src/frontend/AppShell";
import { app } from "./src/backend";
// import { AppShell } from "./src/frontend/AppShell";
// import { app } from "./src/backend";
const index = new Elysia()
.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);
// const index = new Elysia()
// .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}`
);
// 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 route = `/${file.replace(/\.md$/, '')}`;
routes[route] = post.default;
}
serve({
routes,
development: true,
})

4
markdown.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "*.md" {
const content: import("html").HtmlString;
export default content;
}

View File

@ -8,7 +8,9 @@
"dependencies": {
"@elysiajs/html": "1.4.0",
"@elysiajs/static": "1.4.4",
"elysia": "^1.4.11"
"elysia": "^1.4.11",
"gray-matter": "^4.0.3",
"marked": "^16.4.1"
},
"devDependencies": {
"@types/bun": "^1.3.0"

View File

@ -25,7 +25,7 @@
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "ES2022", /* Specify what module code is generated. */
"module": "esnext", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */