Update README.md

This commit is contained in:
Benjamin Toby 2026-03-21 14:36:32 +01:00
parent 4b79993d37
commit 13bd8bb851
2 changed files with 106 additions and 68 deletions

106
README.md
View File

@ -7,6 +7,7 @@ A server-rendering framework for React, built on [Bun](https://bun.sh). Bunext h
Bunext is focused on **server-side rendering and processing**. Every page is rendered on the server on every request. The framework deliberately does not implement client-side navigation, SPA routing, or client-side state management — those concerns belong in client-side libraries and are orthogonal to what Bunext is solving. Bunext is focused on **server-side rendering and processing**. Every page is rendered on the server on every request. The framework deliberately does not implement client-side navigation, SPA routing, or client-side state management — those concerns belong in client-side libraries and are orthogonal to what Bunext is solving.
The goal is a framework that is: The goal is a framework that is:
- Fast — Bun's runtime speed and ESBuild's bundling make the full dev loop snappy - Fast — Bun's runtime speed and ESBuild's bundling make the full dev loop snappy
- Transparent — the entire request pipeline is readable and debugable - Transparent — the entire request pipeline is readable and debugable
- Standard — server functions and API handlers use native Web APIs (`Request`, `Response`, `URL`) with no custom wrappers - Standard — server functions and API handlers use native Web APIs (`Request`, `Response`, `URL`) with no custom wrappers
@ -72,11 +73,10 @@ Install Bunext directly from GitHub:
bun add github:moduletrace/bunext bun add github:moduletrace/bunext
``` ```
Install required peer dependencies: Install globally:
```bash ```bash
bun add react react-dom bun add -g github:moduletrace/bunext
bun add -d typescript @types/react @types/react-dom
``` ```
--- ---
@ -102,13 +102,25 @@ export default function HomePage() {
} }
``` ```
3. Start the development server: 3. Add scripts to your `package.json`:
```bash ```json
bunext dev {
"scripts": {
"dev": "bunx bunext dev",
"build": "bunx bunext build",
"start": "bunx bunext start"
}
}
``` ```
4. Open `http://localhost:7000` in your browser. 4. Start the development server:
```bash
bun run dev
```
5. Open `http://localhost:7000` in your browser.
--- ---
@ -120,18 +132,38 @@ bunext dev
| `bunext build` | Bundle all pages for production. Outputs artifacts to `public/pages/`. | | `bunext build` | Bundle all pages for production. Outputs artifacts to `public/pages/`. |
| `bunext start` | Start the production server using pre-built artifacts. | | `bunext start` | Start the production server using pre-built artifacts. |
### Running the CLI
Bunext exposes a `bunext` binary. How you invoke it depends on how the package is installed:
**Local install (recommended)** — add scripts to `package.json` and run them with `bun run`:
```json
{
"scripts": {
"dev": "bunx bunext dev",
"build": "bunx bunext build",
"start": "bunx bunext start"
}
}
```
```bash ```bash
# Development bun run dev
bun run build
bun run start
```
**Global install** — install once and use `bunext` from anywhere:
```bash
bun add -g github:moduletrace/bunext
bunext dev bunext dev
# Production build
bunext build bunext build
# Production server (must run build first)
bunext start bunext start
``` ```
> **Note:** `bunext start` will exit with an error if `public/pages/map.json` does not exist. Always run `bunext build` before `bunext start`. > **Note:** `bunext start` will exit with an error if `public/pages/map.json` does not exist. Always run `bunext build` (or `bun run build`) before `bunext start`.
--- ---
@ -185,7 +217,7 @@ Dynamic route parameters (e.g. `[slug]`) are available in the `server` function
Directories whose name contains `--` or a parenthesis (`(` or `)`) are completely ignored by the router. Use this to co-locate helper components, utilities, or shared logic directly inside `src/pages/` alongside the routes that use them, without them becoming routes. Directories whose name contains `--` or a parenthesis (`(` or `)`) are completely ignored by the router. Use this to co-locate helper components, utilities, or shared logic directly inside `src/pages/` alongside the routes that use them, without them becoming routes.
| Naming pattern | Effect | | Naming pattern | Effect |
| --- | --- | | --------------- | -------------------- |
| `(components)/` | Ignored — not routed | | `(components)/` | Ignored — not routed |
| `--utils--/` | Ignored — not routed | | `--utils--/` | Ignored — not routed |
| `--lib/` | Ignored — not routed | | `--lib/` | Ignored — not routed |
@ -222,7 +254,7 @@ Export a `server` function to run server-side logic before rendering. The return
```tsx ```tsx
// src/pages/profile.tsx // src/pages/profile.tsx
import type { BunextPageServerFn } from "bunext/src/types"; import type { BunextPageServerFn } from "@moduletrace/bunext/types";
type Props = { type Props = {
props?: { username: string; bio: string }; props?: { username: string; bio: string };
@ -274,7 +306,7 @@ Every page component automatically receives a `url` prop — a copy of the reque
```tsx ```tsx
// src/pages/index.tsx // src/pages/index.tsx
import type { BunextPageModuleServerReturnURLObject } from "bunext/src/types"; import type { BunextPageModuleServerReturnURLObject } from "@moduletrace/bunext/types";
type Props = { type Props = {
url?: BunextPageModuleServerReturnURLObject; url?: BunextPageModuleServerReturnURLObject;
@ -293,7 +325,7 @@ export default function HomePage({ url }: Props) {
The `url` prop exposes the following fields from the standard Web `URL` interface: The `url` prop exposes the following fields from the standard Web `URL` interface:
| Field | Type | Example | | Field | Type | Example |
| -------------- | ---------------- | -------------------------------- | | -------------- | ----------------- | -------------------------------- |
| `href` | `string` | `"https://example.com/blog?q=1"` | | `href` | `string` | `"https://example.com/blog?q=1"` |
| `origin` | `string` | `"https://example.com"` | | `origin` | `string` | `"https://example.com"` |
| `protocol` | `string` | `"https:"` | | `protocol` | `string` | `"https:"` |
@ -354,7 +386,7 @@ export const server: BunextPageServerFn = async (ctx) => {
Export a `meta` object to inject SEO and Open Graph tags into the `<head>`: Export a `meta` object to inject SEO and Open Graph tags into the `<head>`:
```tsx ```tsx
import type { BunextPageModuleMeta } from "bunext/src/types"; import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
export const meta: BunextPageModuleMeta = { export const meta: BunextPageModuleMeta = {
title: "My Page Title", title: "My Page Title",
@ -393,7 +425,7 @@ export default function AboutPage() {
`meta` can also be an async function that receives the request context and server response: `meta` can also be an async function that receives the request context and server response:
```tsx ```tsx
import type { BunextPageModuleMetaFn } from "bunext/src/types"; import type { BunextPageModuleMetaFn } from "@moduletrace/bunext/types";
export const meta: BunextPageModuleMetaFn = async ({ ctx, serverRes }) => { export const meta: BunextPageModuleMetaFn = async ({ ctx, serverRes }) => {
return { return {
@ -408,7 +440,7 @@ export const meta: BunextPageModuleMetaFn = async ({ ctx, serverRes }) => {
Export a `Head` functional component to inject arbitrary HTML into `<head>`. It receives the server response and request context: Export a `Head` functional component to inject arbitrary HTML into `<head>`. It receives the server response and request context:
```tsx ```tsx
import type { BunextPageHeadFCProps } from "bunext/src/types"; import type { BunextPageHeadFCProps } from "@moduletrace/bunext/types";
export function Head({ serverRes, ctx }: BunextPageHeadFCProps) { export function Head({ serverRes, ctx }: BunextPageHeadFCProps) {
return ( return (
@ -454,7 +486,7 @@ Create files under `src/pages/api/` to define API endpoints. The default export
```ts ```ts
// src/pages/api/hello.ts // src/pages/api/hello.ts
import type { BunxRouteParams } from "bunext/src/types"; import type { BunxRouteParams } from "@moduletrace/bunext/types";
export default async function handler(ctx: BunxRouteParams): Promise<Response> { export default async function handler(ctx: BunxRouteParams): Promise<Response> {
return Response.json({ message: "Hello from the API" }); return Response.json({ message: "Hello from the API" });
@ -465,7 +497,7 @@ API routes are matched at `/api/<filename>`. Because the handler returns a plain
```ts ```ts
// src/pages/api/users.ts // src/pages/api/users.ts
import type { BunxRouteParams } from "bunext/src/types"; import type { BunxRouteParams } from "@moduletrace/bunext/types";
export default async function handler(ctx: BunxRouteParams): Promise<Response> { export default async function handler(ctx: BunxRouteParams): Promise<Response> {
if (ctx.req.method !== "GET") { if (ctx.req.method !== "GET") {
@ -488,7 +520,7 @@ Export a `config` object to override the per-route request body limit (default:
import type { import type {
BunextServerRouteConfig, BunextServerRouteConfig,
BunxRouteParams, BunxRouteParams,
} from "bunext/src/types"; } from "@moduletrace/bunext/types";
export const config: BunextServerRouteConfig = { export const config: BunextServerRouteConfig = {
maxRequestBodyMB: 50, // allow up to 50 MB maxRequestBodyMB: 50, // allow up to 50 MB
@ -553,7 +585,7 @@ Bunext includes a file-based HTML cache for production. Caching is **disabled in
Cache files are stored in `public/__bunext/cache/`. Each cached page produces two files: Cache files are stored in `public/__bunext/cache/`. Each cached page produces two files:
| File | Contents | | File | Contents |
|---------------------------|----------------------------------------------| | ----------------- | ---------------------------------------------- |
| `<key>.res.html` | The cached HTML response body | | `<key>.res.html` | The cached HTML response body |
| `<key>.meta.json` | Metadata: creation timestamp, expiry, paradigm | | `<key>.meta.json` | Metadata: creation timestamp, expiry, paradigm |
@ -565,7 +597,7 @@ Export a `config` object from a page file to opt that page into caching:
```tsx ```tsx
// src/pages/products.tsx // src/pages/products.tsx
import type { BunextRouteConfig } from "bunext/src/types"; import type { BunextRouteConfig } from "@moduletrace/bunext/types";
export const config: BunextRouteConfig = { export const config: BunextRouteConfig = {
cachePage: true, cachePage: true,
@ -582,7 +614,7 @@ export default function ProductsPage() {
Cache settings can also be returned from the `server` function, which lets you conditionally enable caching based on request data: Cache settings can also be returned from the `server` function, which lets you conditionally enable caching based on request data:
```tsx ```tsx
import type { BunextPageServerFn } from "bunext/src/types"; import type { BunextPageServerFn } from "@moduletrace/bunext/types";
export const server: BunextPageServerFn = async (ctx) => { export const server: BunextPageServerFn = async (ctx) => {
const data = await fetchProducts(); const data = await fetchProducts();
@ -595,7 +627,13 @@ export const server: BunextPageServerFn = async (ctx) => {
}; };
export default function ProductsPage({ props }: any) { export default function ProductsPage({ props }: any) {
return <ul>{props.data.map((p: any) => <li key={p.id}>{p.name}</li>)}</ul>; return (
<ul>
{props.data.map((p: any) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
} }
``` ```
@ -627,7 +665,7 @@ Create a `bunext.config.ts` file in your project root to configure Bunext:
```ts ```ts
// bunext.config.ts // bunext.config.ts
import type { BunextConfig } from "bunext/src/types"; import type { BunextConfig } from "@moduletrace/bunext/types";
const config: BunextConfig = { const config: BunextConfig = {
port: 3000, // default: 7000 port: 3000, // default: 7000
@ -644,7 +682,7 @@ export default config;
``` ```
| Option | Type | Default | Description | | Option | Type | Default | Description |
| -------------- | --------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------- | | -------------------- | --------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------- |
| `port` | `number` | `7000` | HTTP server port | | `port` | `number` | `7000` | HTTP server port |
| `origin` | `string` | — | Canonical origin URL | | `origin` | `string` | — | Canonical origin URL |
| `distDir` | `string` | `.bunext` | Internal artifact directory | | `distDir` | `string` | `.bunext` | Internal artifact directory |
@ -668,7 +706,7 @@ Middleware runs on every request before any routing. Define it in `bunext.config
import type { import type {
BunextConfig, BunextConfig,
BunextConfigMiddlewareParams, BunextConfigMiddlewareParams,
} from "bunext/src/types"; } from "@moduletrace/bunext/types";
const config: BunextConfig = { const config: BunextConfig = {
middleware: async ({ req, url }) => { middleware: async ({ req, url }) => {
@ -721,7 +759,7 @@ export const BunextWebsocket: WebSocketHandler<any> = {
```ts ```ts
// bunext.config.ts // bunext.config.ts
import type { BunextConfig } from "bunext/src/types"; import type { BunextConfig } from "@moduletrace/bunext/types";
import { BunextWebsocket } from "./websocket"; import { BunextWebsocket } from "./websocket";
const config: BunextConfig = { const config: BunextConfig = {
@ -737,7 +775,7 @@ Pass additional options to the underlying `Bun.serve()` call via `serverOptions`
```ts ```ts
// bunext.config.ts // bunext.config.ts
import type { BunextConfig } from "bunext/src/types"; import type { BunextConfig } from "@moduletrace/bunext/types";
const config: BunextConfig = { const config: BunextConfig = {
serverOptions: { serverOptions: {
@ -764,7 +802,7 @@ For full control over the `Bun.serve()` instance — custom WebSocket upgrade lo
```ts ```ts
// server.ts // server.ts
import bunext from "bunext"; import bunext from "@moduletrace/bunext";
const development = process.env.NODE_ENV === "development"; const development = process.env.NODE_ENV === "development";
const port = process.env.PORT || 3700; const port = process.env.PORT || 3700;
@ -787,7 +825,7 @@ bunext.bunextLog.info(`Server running on http://localhost:${server.port} ...`);
``` ```
| Export | Type | Description | | Export | Type | Description |
|--------|------|-------------| | ------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `bunextInit()` | `() => Promise<void>` | Initializes config, router, and bundler. Must be called before handling requests. | | `bunextInit()` | `() => Promise<void>` | Initializes config, router, and bundler. Must be called before handling requests. |
| `bunextRequestHandler({ req })` | `(params: { req: Request }) => Promise<Response>` | The main Bunext request dispatcher — middleware, routing, SSR, static files. Only `req` is needed; the server instance is managed internally. | | `bunextRequestHandler({ req })` | `(params: { req: Request }) => Promise<Response>` | The main Bunext request dispatcher — middleware, routing, SSR, static files. Only `req` is needed; the server instance is managed internally. |
| `bunextLog` | Logger | Framework logger (`info`, `error`, `success`, `server`, `watch`). | | `bunextLog` | Logger | Framework logger (`info`, `error`, `success`, `server`, `watch`). |

View File

@ -27,7 +27,7 @@
], ],
"scripts": { "scripts": {
"dev": "tsc --watch", "dev": "tsc --watch",
"git:push": "tsc --noEmit && tsc && git add . && git commit -m 'Bugfix. Update generate-web-html function' && git push", "git:push": "tsc --noEmit && tsc && git add . && git commit -m 'Update README.md' && git push",
"compile": "bun build ./src/commands/index.ts --compile --outfile bin/bunext", "compile": "bun build ./src/commands/index.ts --compile --outfile bin/bunext",
"build": "tsc", "build": "tsc",
"test": "bun test --max-concurrency=1" "test": "bun test --max-concurrency=1"