# Melina.js Architecture

**Technical internals of the Melina.js web framework**

---

## Overview

```
┌─────────────────────────────────────────────────────────────────┐
│                    Traditional Approach                         │
│  Source → Webpack → Babel → PostCSS → AST → Bundle → Disk      │
│                     (multiple processes, complex config)        │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Melina.js Approach                           │
│  Source → Bun.build() → Memory → Serve                         │
│           (single process, zero config)                        │
└─────────────────────────────────────────────────────────────────┘
```

Melina renders pages on the server with JSX. Client interactivity is added via **mount scripts** — vanilla JSX that compiles to real DOM elements.

---

## Request Flow

```
                    HTTP Request
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│                   serve() + createAppRouter()                   │
│                                                                 │
│  1. Match URL to route (file-based)                            │
│  2. Check for built asset (/script-abc123.js)                  │
│  3. Check for static file (app/public/*)                       │
│  4. Check for API route (app/api/*/route.ts)                   │
│  5. Render page (layout + page → HTML)                         │
│  6. Build client scripts (.client.tsx → JS)                    │
│  7. Inject bootstrap script that auto-invokes mount()          │
│  8. Return HTML response                                       │
└─────────────────────────────────────────────────────────────────┘
```

---

## Server vs Client

### Server Graph (Bun Runtime)

- **layout.tsx** and **page.tsx** render on the server
- Can use: async/await, databases, file system, env vars
- Cannot use: DOM APIs, window, event listeners
- Output: HTML string

### Client Graph (Browser)

- **page.client.tsx** and **layout.client.tsx** run in the browser
- JSX compiles to **real DOM elements** via `jsx-dom.ts` (not React VDOM)
- Export a `mount()` function — Melina auto-invokes it on page load
- Return a cleanup function for teardown on navigation

```
┌─────────────────────────────────────────────────────────────────┐
│                     SERVER (Bun)                                │
│  ┌─────────────┐   ┌─────────────┐   ┌──────────────┐          │
│  │  layout.tsx │ → │  page.tsx   │ → │  HTML string │ ─────── │──→ Browser
│  └─────────────┘   └─────────────┘   └──────────────┘          │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                     BROWSER                                     │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  Server-rendered HTML                                      │ │
│  │  + layout.client.tsx mount() ← persistent across pages     │ │
│  │  + page.client.tsx mount()   ← per-page, cleans up on nav │ │
│  └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```

---

## Mount Script Lifecycle

Mount scripts export a default `mount()` function. Melina injects an inline bootstrap script that dynamically imports each client module and calls its default export.

```html
<!-- Generated by the framework -->
<script type="module">
  import('/layout.client-abc123.js').then(m => {
    if (typeof m.default === 'function') {
      const cleanup = m.default();
      // cleanup stored for teardown
    }
  });
  import('/page.client-def456.js').then(m => {
    if (typeof m.default === 'function') {
      const cleanup = m.default();
    }
  });
</script>
```

### JSX in Mount Scripts

Client JSX is compiled with `jsx-dom.ts`, which replaces `react/jsx-runtime` during the build. This means JSX creates real DOM elements:

```tsx
// This JSX:
const el = <div className="toast"><span>Hello</span></div>;

// Compiles to:
const el = jsx("div", { className: "toast", children: jsx("span", { children: "Hello" }) });
// Where jsx() calls document.createElement() and returns a real DOM node

// So you can do:
document.body.appendChild(el); // Works directly!
```

---

## In-Memory Build System

All assets are built with `Bun.build()` and stored in memory. No `dist/` directory.

```typescript
const result = await Bun.build({
  entrypoints: [clientPath],
  outdir: undefined,     // ← Key: keeps output in memory
  target: 'browser',
  minify: !isDev,
  naming: {
    entry: '[name]-[hash].[ext]',  // Content-hashed filenames
  },
});

// Store in RAM
builtAssets[outputPath] = { content, contentType };
```

### Build Deduplication

Concurrent requests for the same client script wait on a shared Promise instead of triggering parallel builds:

```typescript
const buildInFlight = new Map<string, Promise<string>>();

async function buildClientScript(path: string) {
  const existing = buildInFlight.get(path);
  if (existing) return existing;

  const promise = _buildClientScriptImpl(path);
  buildInFlight.set(path, promise);
  try { return await promise; }
  finally { buildInFlight.delete(path); }
}
```

### CSS Pipeline

CSS is processed with PostCSS + autoprefixer + Tailwind CSS v4:

```typescript
const result = await postcss([
  autoprefixer,
  tailwind,
]).process(cssContent, { from: cssPath });
```

---

## File-Based Routing

Routes are discovered at startup by scanning the `app/` directory.

| File Pattern | URL | Type |
|---|---|---|
| `app/page.tsx` | `/` | Page |
| `app/about/page.tsx` | `/about` | Page |
| `app/post/[id]/page.tsx` | `/post/:id` | Dynamic |
| `app/api/messages/route.ts` | `/api/messages` | API |
| `app/layout.tsx` | — | Layout |

### Nested Layouts

Layouts compose from outermost to innermost:

```typescript
let tree = createElement(PageComponent, { params });

for (let i = layouts.length - 1; i >= 0; i--) {
  const Layout = (await import(layouts[i])).default;
  tree = createElement(Layout, { children: tree });
}

// Render to HTML
const html = ReactDOMServer.renderToString(tree);
```

---

## Unix Socket Support

For production behind a reverse proxy (Nginx/Caddy), Melina supports Unix Domain Sockets:

```
TCP Loopback:       App → TCP Stack → Routing Tables → Nginx
Unix Socket:        App → Kernel Buffer (direct copy) → Nginx
```

Set `BUN_PORT=/tmp/melina.sock` to enable. Socket cleanup is handled automatically on SIGINT/SIGTERM.

---

## Key Files

| File | Purpose |
|---|---|
| `src/web.ts` | Main server framework (serve, createAppRouter, start, build*) |
| `src/router.ts` | File-based route discovery and URL matching |
| `src/jsx-dom.ts` | Client JSX runtime — JSX → real DOM elements |
| `src/Link.tsx` | `<Link>` component for navigation |
| `src/utils.ts` | Utilities (measure, etc.) |
| `bin/melina` | CLI (init, start) |
