# 🎪 hype

    ░▒▓███████████████████████████████████████████████▓▒░
    ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░
    ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
    ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
    ░▒▓████████▓▒░░▒▓██████▓▒░░▒▓███████▓▒░░▒▓██████▓▒░
    ░▒▓█▓▒░░▒▓█▓▒░  ░▒▓█▓▒░   ░▒▓█▓▒░      ░▒▓█▓▒░
    ░▒▓█▓▒░░▒▓█▓▒░  ░▒▓█▓▒░   ░▒▓█▓▒░      ░▒▓█▓▒░
    ░▒▓█▓▒░░▒▓█▓▒░  ░▒▓█▓▒░   ░▒▓█▓▒░      ░▒▓████████▓▒░
    ░▒▓███████████████████████████████████████████████▓▒░

`hype` wraps `hono` with useful features for fast prototyping:

- HTTP logging to the console (disable with `NO_HTTP_LOG` env variable)
- Auto-port selection in dev mode: if the port is busy, tries the next one (disable with `NO_AUTOPORT` env variable)
- Static files served from `pub/`
- Page-based routing to `.tsx` files that export a `JSX` function in `./src/pages`
- Transpile `.ts` files in `src/client/blah.ts` via `website.com/client/blah.ts`
- Default, simple HTML5 layout with working frontend transpilation/bundling, or supply your own.
- Live reload in development mode — pages automatically refresh when the server restarts.
- Optional CSS reset.
- Optional pico.css.

## Overview

Your project structure should be:

```
.
├── README.md
├── package.json
├── pub
│   ├── asset.txt
│   └── img
│       └── logo.png
├── src
│   ├── css
│   │   └── main.css
│   ├── client
│   │   ├── about.ts
│   │   └── main.ts
│   ├── shared
│   │   └── utils.ts
│   ├── pages
│   │   ├── _layout.tsx
│   │   ├── about.tsx
│   │   └── index.tsx
│   └── server
│       └── index.ts
└── tsconfig.json
```

### URLs

In the above, `/about` will render `./src/pages/about.tsx`.

The `req` JSX prop will be given the `Hono` request:

    export default ({ req }) => `Query Param 'id': ${req.query('id')}`

To put a file in `./src/pages` but prevent it from being server, preface it with `_`.

### Layout

`hype` will wrap everything in a simple default layout unless `./src/pages/_layout.tsx` exists,
in which case it'll use the default export in that file.

You can also create hype with `new Hype({ layout: false })` to disable Hype's default.

Using the default layout, you can put TS in `./src/client/main.ts` and css in `./src/css/main.css`.
They'll automatically work in your app.

Here's a starting point:

```tsx
export default ({ children, title }: any) => (
  <html lang="en">
    <head>
      <title>{title ?? "hype"}</title>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />

      <link href="/css/main.css" rel="stylesheet" />
      <script src="/client/main.ts" type="module"></script>
    </head>
    <body>
      <main>{children}</main>
    </body>
  </html>
)
```

### pico.css

To use pico.css with the default layout, create `Hype` with `{ pico: true }`:

```typescript
import { Hype } from "hype"

const app = new Hype({ pico: true })
```

You can also use pico in your custom layouts:

```html
<link href="/css/pico.css" rel="stylesheet" />
```

### css

CSS can be accessed via `/css/main.css`:

```html
<link href="/css/main.css" rel="stylesheet" />
```

### css reset

To use reset.css with the default layout, create `Hype` with `{ reset: true }`:

```typescript
import { Hype } from "hype"

const app = new Hype({ reset: true })
```

You can also use the css reset in your custom layouts:

```html
<link href="/css/reset.css" rel="stylesheet" />
```

### js

JS can be accessed (and transpiled) via `/client/main.ts` or `/shared/utils.ts`:

```html
<script src="/client/main.ts" type="module"></script>
```

import your modules relatively, for example in `main.ts`:

```typescript
import { initAbout } from "./about"
import utils from "./shared/utils"
```

### pub

Anything in `pub/` is served as-is. Simple stuff.

### Server-Sent Events (SSE)

`hype` provides a simple `app.sse()` helper for streaming data to clients:

```typescript
import { Hype } from "hype"

const app = new Hype()

// Stream current time every second
app.sse('/api/time', (send) => {
  send({ time: Date.now() })
  const interval = setInterval(() => send({ time: Date.now() }), 1000)
  return () => clearInterval(interval)  // cleanup on disconnect
})
```

The `send()` function:
- Automatically JSON.stringify's objects (strings are sent as-is)
- Accepts an optional second parameter for named events: `send(data, 'eventName')`

The handler can return a cleanup function that's called when the client disconnects.

You also have access to the Hono context as the second parameter:

```typescript
app.sse('/api/user-events', (send, c) => {
  const userId = c.req.query('userId')
  // ... subscribe to user-specific events
})
```

**Client-side usage:**

```typescript
const events = new EventSource('/api/time')

events.onmessage = (e) => {
  const data = JSON.parse(e.data)
  console.log('Time:', data.time)
}

// For named events:
events.addEventListener('eventName', (e) => {
  console.log('Got event:', e.data)
})
```

Test with curl:

```sh
curl -N http://localhost:3000/api/time
```

### Live Reload

In development mode (`NODE_ENV !== 'production'`), `hype` automatically injects a live reload script into pages using the default layout. When the server restarts (e.g. via `bun --hot`), the browser will automatically reload the page.

If you're using a custom layout, you can add live reload by importing and rendering the `ReloadScript` component:

```tsx
import { ReloadScript } from "hype"

export default ({ children, title }: any) => (
  <html lang="en">
    <head>
      <title>{title ?? "hype"}</title>
    </head>
    <body>
      <main>{children}</main>
      <ReloadScript />
    </body>
  </html>
)
```

`ReloadScript` only renders in development mode — it's a no-op in production.

### utils

`hype` includes helpful utils for your webapp:

- `capitalize(str: string): string` - Capitalizes a word
- `darkenColor(hex: string, opacity: number): string` - Darken a hex color by blending with black. opacity 1 = original, 0 = black
- `isDarkMode(): boolean` - Check if the user prefers dark mode
- `lightenColor(hex: string, opacity: number): string` - Lighten a hex color by blending with white. opacity 1 = original, 0 = white
- `rand(end = 2, startAtZero = false): number` - Generate random integer. `rand(2)` flips a coin, `rand(6)` rolls a die, `rand(20)` rolls d20
- `randIndex<T>(list: T[]): number | undefined` - Get a random index from an array. `randIndex([5, 7, 9])` returns `0`, `1`, or `2`
- `randItem<T>(list: T[]): T | undefined` - Get a random item from an array. `randItem([5, 7, 9])` returns `5`, `7`, or `9`
- `randomId(): string` - Generate a 6 character random ID
- `randRange(start = 0, end = 12): number` - Generate random integer in range. `randRange(1, 6)` rolls a die
- `redirectBack(c: Context, fallback = "/"): Response` - Redirect to the referrer, or fallback if no referrer
- `times(n: number): number[]` - Generate array of integers. `times(3)` returns `[1, 2, 3]`
- `transpile(path: string): Promise<string>` - Transpile a TypeScript file at `path` to JavaScript (cached by mtime)
- `unique<T>(array: T[]): T[]` - Remove duplicates from array. `unique([1,1,2,2,3,3])` returns `[1,2,3]`
- `weightedRand(): number` - Generate random number between 1 and 10 with decreasing probability (1 is most likely, 10 is least)

## Setup

0. Add the dependency to your project:

```sh
bun add git+https://git.nose.space/defunkt/hype
```

1. Add `dev` and `start` to `package.json`:

```json
  "scripts": {
    "start": "bun run src/server/index.ts",
    "dev": "bun run --hot src/server/index.ts"
  },
```

2. Use our `tsconfig.json`:

```json
{
  "compilerOptions": {
    "lib": ["ESNext", "DOM"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "jsxImportSource": "hono/jsx",
    "allowJs": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false,
    "baseUrl": ".",
    "paths": {
      "$*": ["src/server/*"],
      "#*": ["src/client/*"],
      "@*": ["src/shared/*"]
    }
  }
}
```

3. Use `Hype` just like `Hono` in your `src/server.ts`, exporting `app.defaults`:

```typescript
import { Hype } from "hype"

const app = new Hype()

app.get("/my-custom-routes", (c) => c.text("wild, wild stuff"))

export default app.defaults
```

4. Enter dev mode

```sh
bun install
bun dev
```
