Compare commits
2 Commits
c968c7aefc
...
ebcce8dea1
| Author | SHA1 | Date | |
|---|---|---|---|
| ebcce8dea1 | |||
| 275527e30e |
101
README.md
101
README.md
|
|
@ -10,12 +10,16 @@
|
||||||
░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓████████▓▒░
|
░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓████████▓▒░
|
||||||
░▒▓███████████████████████████████████████████████▓▒░
|
░▒▓███████████████████████████████████████████████▓▒░
|
||||||
|
|
||||||
`hype` wraps `hono` with useful defaults:
|
`hype` wraps `hono` with useful features for fast prototyping:
|
||||||
|
|
||||||
- HTTP logging (disable with `NO_HTTP_LOG` env variable)
|
- HTTP logging (disable with `NO_HTTP_LOG` env variable)
|
||||||
- Static files served from `pub/`
|
- Static files served from `pub/`
|
||||||
- Page-based routing to `.tsx` files that export a `JSX` function in `./src/pages`
|
- Page-based routing to `.tsx` files that export a `JSX` function in `./src/pages`
|
||||||
- Transpile `.ts` files in `src/js/blah.ts` via `website.com/js/blah.ts`
|
- Transpile `.ts` files in `src/js/blah.ts` via `website.com/js/blah.ts`
|
||||||
|
- Helpers like `css` and `js` template tags.
|
||||||
|
- Default, simple HTML5 layout with working frontend transpilation/bundling, or supply your own.
|
||||||
|
- Optional CSS reset.
|
||||||
|
- Optional pico.css.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|
@ -60,6 +64,9 @@ To put a file in `./src/pages` but prevent it from being server, preface it with
|
||||||
`hype` will wrap everything in a simple default layout unless `./src/pages/_layout.tsx` exists,
|
`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.
|
in which case it'll use the default export in that file.
|
||||||
|
|
||||||
|
Using the default layout, you can put TS in `./src/js/main.ts` and css in `./src/css/main.css`
|
||||||
|
and they'll automatically work in your app.
|
||||||
|
|
||||||
Here's a starting point:
|
Here's a starting point:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
|
|
@ -80,6 +87,22 @@ export default ({ children, title }: any) => (
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
CSS can be accessed via `/css/main.css`:
|
CSS can be accessed via `/css/main.css`:
|
||||||
|
|
@ -88,9 +111,34 @@ CSS can be accessed via `/css/main.css`:
|
||||||
<link href="/css/main.css" rel="stylesheet" />
|
<link href="/css/main.css" rel="stylesheet" />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or written inline using the `css` template tag:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { css } from "hype"
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<div>
|
||||||
|
{css`
|
||||||
|
* {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
<h1>Hello</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
### css reset
|
### css reset
|
||||||
|
|
||||||
`hype` includes a css reset for your convenience:
|
|
||||||
|
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
|
```html
|
||||||
<link href="/css/reset.css" rel="stylesheet" />
|
<link href="/css/reset.css" rel="stylesheet" />
|
||||||
|
|
@ -111,6 +159,25 @@ import { initAbout } from "./about"
|
||||||
import utils from "./shared/utils"
|
import utils from "./shared/utils"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or written inline as transpiled typescript using the `js` template tag:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { js } from "hype"
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<div>
|
||||||
|
{js`
|
||||||
|
window.onload = () => alert(welcomeMsg(Date.now()))
|
||||||
|
|
||||||
|
function welcomeMsg(time: number): string {
|
||||||
|
return "Welcome to my website!"
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
<h1>Hello!</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
### pub
|
### pub
|
||||||
|
|
||||||
Anything in `pub/` is served as-is. Simple stuff.
|
Anything in `pub/` is served as-is. Simple stuff.
|
||||||
|
|
@ -119,19 +186,22 @@ Anything in `pub/` is served as-is. Simple stuff.
|
||||||
|
|
||||||
`hype` includes helpful utils for your webapp:
|
`hype` includes helpful utils for your webapp:
|
||||||
|
|
||||||
- capitalize
|
- `capitalize(str: string): string` - Capitalizes a word
|
||||||
- darkenColor
|
- `css` Template Tag - Lets you inline CSS in your TSX. Returns a `<style>` tag
|
||||||
- isDarkMode
|
- `darkenColor(hex: string, opacity: number): string` - Darken a hex color by blending with black. opacity 1 = original, 0 = black
|
||||||
- lightenColor
|
- `isDarkMode(): boolean` - Check if the user prefers dark mode
|
||||||
- rand
|
- `js` Template Tag - Lets you inline JavaScript in your TSX. Transpiles and returns a `<script>` tag
|
||||||
- randIndex
|
- `lightenColor(hex: string, opacity: number): string` - Lighten a hex color by blending with white. opacity 1 = original, 0 = white
|
||||||
- randItem
|
- `rand(end = 2, startAtZero = false): number` - Generate random integer. `rand(2)` flips a coin, `rand(6)` rolls a die, `rand(20)` rolls d20
|
||||||
- randomId
|
- `randIndex<T>(list: T[]): number | undefined` - Get a random index from an array. `randIndex([5, 7, 9])` returns `0`, `1`, or `2`
|
||||||
- randRange
|
- `randItem<T>(list: T[]): T | undefined` - Get a random item from an array. `randItem([5, 7, 9])` returns `5`, `7`, or `9`
|
||||||
- redirectBack
|
- `randomId(): string` - Generate a 6 character random ID
|
||||||
- transpile
|
- `randRange(start = 0, end = 12): number` - Generate random integer in range. `randRange(1, 6)` rolls a die
|
||||||
- unique
|
- `redirectBack(c: Context, fallback = "/"): Response` - Redirect to the referrer, or fallback if no referrer
|
||||||
- weightedRand
|
- `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
|
## Setup
|
||||||
|
|
||||||
|
|
@ -194,3 +264,4 @@ export default app.defaults
|
||||||
bun install
|
bun install
|
||||||
bun test
|
bun test
|
||||||
```
|
```
|
||||||
|
`````
|
||||||
|
|
|
||||||
3
bun.lock
3
bun.lock
|
|
@ -6,6 +6,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hono": "^4.10.4",
|
"hono": "^4.10.4",
|
||||||
"kleur": "^4.1.5",
|
"kleur": "^4.1.5",
|
||||||
|
"prettier": "^3.7.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
|
@ -30,6 +31,8 @@
|
||||||
|
|
||||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
||||||
|
"prettier": ["prettier@3.7.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hono": "^4.10.4",
|
"hono": "^4.10.4",
|
||||||
"kleur": "^4.1.5"
|
"kleur": "^4.1.5",
|
||||||
|
"prettier": "^3.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import prettier from 'prettier'
|
||||||
import { type Context, Hono } from 'hono'
|
import { type Context, Hono } from 'hono'
|
||||||
import { serveStatic } from 'hono/bun'
|
import { serveStatic } from 'hono/bun'
|
||||||
import color from 'kleur'
|
import color from 'kleur'
|
||||||
|
|
@ -8,12 +9,23 @@ import defaultLayout from './layout'
|
||||||
|
|
||||||
const SHOW_HTTP_LOG = true
|
const SHOW_HTTP_LOG = true
|
||||||
const CSS_RESET = await Bun.file(join(import.meta.dir, '/reset.css')).text()
|
const CSS_RESET = await Bun.file(join(import.meta.dir, '/reset.css')).text()
|
||||||
|
const PICO_CSS = await Bun.file(join(import.meta.dir, '/pico.css')).text()
|
||||||
|
|
||||||
|
export * from './utils'
|
||||||
|
|
||||||
|
export type HypeProps = {
|
||||||
|
pico?: boolean
|
||||||
|
reset?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export class Hype extends Hono {
|
export class Hype extends Hono {
|
||||||
routesRegistered = false
|
routesRegistered = false
|
||||||
|
props: HypeProps = {}
|
||||||
|
|
||||||
constructor() {
|
constructor(props?: HypeProps) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
if (props) this.props = props
|
||||||
}
|
}
|
||||||
|
|
||||||
registerRoutes() {
|
registerRoutes() {
|
||||||
|
|
@ -38,10 +50,24 @@ export class Hype extends Hono {
|
||||||
console.log(fn(`${c.res.status}`), `${color.bold(method)} ${c.req.url} (${end - start}ms)`)
|
console.log(fn(`${c.res.status}`), `${color.bold(method)} ${c.req.url} (${end - start}ms)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// prettify HTML output
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
this.use('*', async (c, next) => {
|
||||||
|
await next();
|
||||||
|
|
||||||
|
if (c.res.headers.get('content-type')?.includes('text/html')) {
|
||||||
|
const html = await c.res.text();
|
||||||
|
const formatted = await prettier.format(html, { parser: 'html' });
|
||||||
|
c.res = new Response(formatted, c.res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// css reset
|
// css reset
|
||||||
this.get('/css/reset.css', async c => new Response(CSS_RESET, {
|
this.get('/css/reset.css', async c => new Response(CSS_RESET, { headers: { 'Content-Type': 'text/css' } }))
|
||||||
headers: { 'Content-Type': 'text/css' }
|
|
||||||
}))
|
// pico
|
||||||
|
this.get('/css/pico.css', async c => new Response(PICO_CSS, { headers: { 'Content-Type': 'text/css' } }))
|
||||||
|
|
||||||
// serve transpiled js
|
// serve transpiled js
|
||||||
this.on('GET', ['/js/:path{.+}', '/shared/:path{.+}'], async c => {
|
this.on('GET', ['/js/:path{.+}', '/shared/:path{.+}'], async c => {
|
||||||
|
|
@ -77,7 +103,7 @@ export class Hype extends Hono {
|
||||||
|
|
||||||
const page = await import(path + `?t=${Date.now()}`)
|
const page = await import(path + `?t=${Date.now()}`)
|
||||||
const innerHTML = typeof page.default === 'function' ? <page.default req={c.req} /> : page.default
|
const innerHTML = typeof page.default === 'function' ? <page.default req={c.req} /> : page.default
|
||||||
const withLayout = Layout ? <Layout>{innerHTML}</Layout> : innerHTML
|
const withLayout = Layout ? <Layout props={this.props}>{innerHTML}</Layout> : innerHTML
|
||||||
return c.html(withLayout)
|
return c.html(withLayout)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import { type FC } from 'hono/jsx'
|
import { type FC } from 'hono/jsx'
|
||||||
|
|
||||||
const Layout: FC = ({ children, title }) =>
|
const Layout: FC = ({ children, title, props }) =>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{title ?? 'hype'}</title>
|
<title>{title ?? 'hype'}</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
|
||||||
|
{props.reset && <link href="/css/reset.css" rel="stylesheet" />}
|
||||||
|
{props.pico && <link href="/css/pico.css" rel="stylesheet" />}
|
||||||
<link href="/css/main.css" rel="stylesheet" />
|
<link href="/css/main.css" rel="stylesheet" />
|
||||||
<script src="/js/main.ts" type="module"></script>
|
<script src="/js/main.ts" type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
4
src/pico.css
Normal file
4
src/pico.css
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user