Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e45bd1d0c | |||
| 78eef47f46 | |||
| 1333deb5e9 | |||
| 45139e519d | |||
| 268cb9482a | |||
| 1b908f8ba3 | |||
| 8785cfe43c | |||
| add241a405 | |||
| 4ff0727006 | |||
| f6da338b46 | |||
| 7e17ab5751 | |||
|
|
1b19e09e14 | ||
|
|
3744454076 | ||
| 1354e41375 | |||
| e8c5eb9d85 | |||
| 1576d92efc | |||
| 834ae8623a | |||
| 5ed8673274 | |||
| 65ee95ca0c | |||
| b9b5641b26 | |||
|
|
06705bab5c | ||
|
|
fec3e69ac0 | ||
|
|
83b03fbdcd | ||
| a7ad3a5c1d | |||
| 85fa79518c | |||
| c054ca1f82 |
44
README.md
44
README.md
|
|
@ -13,10 +13,10 @@
|
|||
`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`
|
||||
- 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.
|
||||
|
|
@ -114,25 +114,6 @@ CSS can be accessed via `/css/main.css`:
|
|||
<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>
|
||||
)
|
||||
```
|
||||
|
||||
Install the `vscode-styled-components` VSCode extension to highlight inline `css` tags!
|
||||
|
||||
### css reset
|
||||
|
||||
To use reset.css with the default layout, create `Hype` with `{ reset: true }`:
|
||||
|
|
@ -164,27 +145,6 @@ import { initAbout } from "./about"
|
|||
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>
|
||||
)
|
||||
```
|
||||
|
||||
Install the `vscode-styled-components` VSCode extension to highlight inline `js` tags!
|
||||
|
||||
### pub
|
||||
|
||||
Anything in `pub/` is served as-is. Simple stuff.
|
||||
|
|
@ -248,10 +208,8 @@ curl -N http://localhost:3000/api/time
|
|||
`hype` includes helpful utils for your webapp:
|
||||
|
||||
- `capitalize(str: string): string` - Capitalizes a word
|
||||
- `css` Template Tag - Lets you inline CSS in your TSX. Returns a `<style>` tag
|
||||
- `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
|
||||
- `js` Template Tag - Lets you inline JavaScript in your TSX. Transpiles and returns a `<script>` tag
|
||||
- `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`
|
||||
|
|
|
|||
1055
docs/GUIDE.md
Normal file
1055
docs/GUIDE.md
Normal file
File diff suppressed because it is too large
Load Diff
2324
docs/HYPE+FORGE.md
Normal file
2324
docs/HYPE+FORGE.md
Normal file
File diff suppressed because it is too large
Load Diff
1
examples/spa/.npmrc
Normal file
1
examples/spa/.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
registry=https://npm.nose.space
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "hype-spa-example",
|
||||
"dependencies": {
|
||||
"hype": "git+https://git.nose.space/defunkt/hype",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
||||
|
||||
"hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="],
|
||||
|
||||
"hype": ["hype@git+https://git.nose.space/defunkt/hype#33d228c9ac6a01fad570e0ac2ba836a100dde623", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "33d228c9ac6a01fad570e0ac2ba836a100dde623"],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
|
||||
"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=="],
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"hype": "git+https://git.nose.space/defunkt/hype"
|
||||
"@because/hype": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "bun run src/server/index.ts",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Hype } from 'hype'
|
||||
import { Hype } from '@because/hype'
|
||||
|
||||
const app = new Hype({ layout: false })
|
||||
|
||||
|
|
|
|||
1
examples/ssr/.npmrc
Normal file
1
examples/ssr/.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
registry=https://npm.nose.space
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "hype-ssr-example",
|
||||
"dependencies": {
|
||||
"hype": "git+https://git.nose.space/defunkt/hype",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
||||
|
||||
"hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="],
|
||||
|
||||
"hype": ["hype@git+https://git.nose.space/defunkt/hype#33d228c9ac6a01fad570e0ac2ba836a100dde623", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "33d228c9ac6a01fad570e0ac2ba836a100dde623"],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
|
||||
"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=="],
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"hype": "git+https://git.nose.space/defunkt/hype"
|
||||
"@because/hype": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "bun run src/server/index.ts",
|
||||
"dev": "bun run --hot src/server/index.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import { fe } from 'hype'
|
||||
|
||||
const test = fe(() => {
|
||||
alert('ding dong')
|
||||
})
|
||||
|
||||
export default () => (
|
||||
<section>
|
||||
<a href="#" onclick={test}>test</a>
|
||||
<a href="#" onclick="alert('ding dong')">test</a>
|
||||
</section>
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { js } from 'hype'
|
||||
|
||||
export default () => (
|
||||
<section>
|
||||
<h1>SSE Demo</h1>
|
||||
|
|
@ -12,7 +10,8 @@ export default () => (
|
|||
<a href="/">Back to Home</a>
|
||||
</p>
|
||||
|
||||
{js`
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
const statusEl = document.getElementById('status')
|
||||
const timeEl = document.getElementById('time')
|
||||
const countEl = document.getElementById('count')
|
||||
|
|
@ -36,6 +35,6 @@ export default () => (
|
|||
statusEl.textContent = 'Disconnected'
|
||||
statusEl.style.color = 'red'
|
||||
}
|
||||
`}
|
||||
`}} />
|
||||
</section>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Hype } from 'hype'
|
||||
import { Hype } from '@because/hype'
|
||||
|
||||
const app = new Hype({})
|
||||
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -1,11 +1,21 @@
|
|||
{
|
||||
"name": "hype",
|
||||
"name": "@because/hype",
|
||||
"version": "0.0.6",
|
||||
"module": "src/index.tsx",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
"./utils": "./src/utils.tsx"
|
||||
},
|
||||
"files": [
|
||||
"src/css",
|
||||
"src/frontend.ts",
|
||||
"src/index.tsx",
|
||||
"src/layout.tsx",
|
||||
"src/lib",
|
||||
"src/utils.tsx",
|
||||
"README.md"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
import { AsyncLocalStorage } from 'async_hooks'
|
||||
|
||||
export const fnStorage = new AsyncLocalStorage<{
|
||||
fns: Map<string, string>
|
||||
counter: number
|
||||
}>()
|
||||
|
||||
export function fe<T extends Record<string, unknown>>(
|
||||
fn: (args?: T) => void,
|
||||
args?: T
|
||||
): string {
|
||||
const store = fnStorage.getStore()
|
||||
if (!store) {
|
||||
// Fallback to IIFE if outside request context
|
||||
return args
|
||||
? `(${fn.toString()})(${JSON.stringify(args)})`
|
||||
: `(${fn.toString()})()`
|
||||
}
|
||||
|
||||
const fnStr = fn.toString()
|
||||
|
||||
// Dedupe by function body
|
||||
for (const [name, body] of store.fns)
|
||||
if (body === fnStr)
|
||||
return args ? `${name}(${JSON.stringify(args)})` : `${name}()`
|
||||
|
||||
const name = `frontendFn${store.counter++}`
|
||||
store.fns.set(name, fnStr)
|
||||
|
||||
return args ? `${name}(${JSON.stringify(args)})` : `${name}()`
|
||||
}
|
||||
|
||||
export function feFunctions(): string[] {
|
||||
const store = fnStorage.getStore()
|
||||
if (!store?.fns.size) return []
|
||||
|
||||
return [...store.fns.entries()].map(([name, body]) => {
|
||||
// Handle arrow functions vs regular functions
|
||||
if (body.startsWith('(') || body.startsWith('async ('))
|
||||
return `const ${name} = ${body};`
|
||||
|
||||
// Named function - rename it
|
||||
return body.replace(/^(async\s+)?function\s*\w*/, `$1function ${name}`)
|
||||
})
|
||||
}
|
||||
|
||||
// Keep for backwards compat
|
||||
export const frontend = fe
|
||||
110
src/index.tsx
110
src/index.tsx
|
|
@ -1,4 +1,4 @@
|
|||
import { join } from 'path'
|
||||
import { join, resolve } from 'path'
|
||||
import { render as formatHTML } from './lib/html-formatter'
|
||||
import { type Context, Hono, type Schema, type Env } from 'hono'
|
||||
import { serveStatic } from 'hono/bun'
|
||||
|
|
@ -7,14 +7,12 @@ import color from 'kleur'
|
|||
|
||||
import { transpile } from './utils'
|
||||
import defaultLayout from './layout'
|
||||
import { feFunctions, fnStorage } from './frontend'
|
||||
|
||||
const SHOW_HTTP_LOG = true
|
||||
const CSS_RESET = await Bun.file(join(import.meta.dir, '/css/reset.css')).text()
|
||||
const PICO_CSS = await Bun.file(join(import.meta.dir, '/css/pico.css')).text()
|
||||
|
||||
export * from './utils'
|
||||
export { frontend, frontend as fe } from './frontend'
|
||||
export type { Context } from 'hono'
|
||||
|
||||
const pageCache = new Map()
|
||||
|
|
@ -24,8 +22,12 @@ export type HypeProps = {
|
|||
reset?: boolean
|
||||
prettyHTML?: boolean
|
||||
layout?: boolean
|
||||
logging?: boolean
|
||||
ok?: boolean
|
||||
}
|
||||
|
||||
type InternalProps = HypeProps & { _isRouter?: boolean }
|
||||
|
||||
export type SSEHandler<E extends Env> = (
|
||||
send: (data: unknown, event?: string) => Promise<void>,
|
||||
c: Context<E>
|
||||
|
|
@ -37,12 +39,20 @@ export class Hype<
|
|||
BasePath extends string = '/'
|
||||
> extends Hono<E, S, BasePath> {
|
||||
props: HypeProps
|
||||
middlewareRegistered = false
|
||||
routesRegistered = false
|
||||
|
||||
constructor(props?: HypeProps & ConstructorParameters<typeof Hono<E, S, BasePath>>[0]) {
|
||||
constructor(props?: InternalProps & ConstructorParameters<typeof Hono<E, S, BasePath>>[0]) {
|
||||
super(props)
|
||||
|
||||
this.props = props ?? {}
|
||||
if (!props?._isRouter) {
|
||||
this.registerMiddleware()
|
||||
}
|
||||
}
|
||||
|
||||
static router<E extends Env = Env>(): Hype<E> {
|
||||
return new Hype<E>({ _isRouter: true })
|
||||
}
|
||||
|
||||
sse(path: string, handler: SSEHandler<E>) {
|
||||
|
|
@ -68,9 +78,9 @@ export class Hype<
|
|||
})
|
||||
}
|
||||
|
||||
registerRoutes() {
|
||||
if (this.routesRegistered) return
|
||||
this.routesRegistered = true
|
||||
registerMiddleware() {
|
||||
if (this.middlewareRegistered) return
|
||||
this.middlewareRegistered = true
|
||||
|
||||
// static assets in pub/
|
||||
this.use('/*', serveStatic({ root: './pub' }))
|
||||
|
|
@ -79,16 +89,18 @@ export class Hype<
|
|||
this.use('/css/*', serveStatic({ root: './src' }))
|
||||
|
||||
// console logging
|
||||
this.use('*', async (c, next) => {
|
||||
if (!SHOW_HTTP_LOG) return await next()
|
||||
if (this.props.logging !== false) {
|
||||
this.use('*', async (c, next) => {
|
||||
if (!SHOW_HTTP_LOG) return await next()
|
||||
|
||||
const start = Date.now()
|
||||
await next()
|
||||
const end = Date.now()
|
||||
const fn = c.res.status < 400 ? color.green : c.res.status < 500 ? color.yellow : color.red
|
||||
const method = c.req.method === 'GET' ? color.cyan(c.req.method) : color.magenta(c.req.method)
|
||||
console.log(fn(`${c.res.status}`), `${color.bold(method)} ${c.req.url} (${end - start}ms)`)
|
||||
})
|
||||
const start = Date.now()
|
||||
await next()
|
||||
const end = Date.now()
|
||||
const fn = c.res.status < 400 ? color.green : c.res.status < 500 ? color.yellow : color.red
|
||||
const method = c.req.method === 'GET' ? color.cyan(c.req.method) : color.magenta(c.req.method)
|
||||
console.log(fn(`${c.res.status}`), `${color.bold(method)} ${c.req.url} (${end - start}ms)`)
|
||||
})
|
||||
}
|
||||
|
||||
// exception handler
|
||||
this.onError((err, c) => {
|
||||
|
|
@ -117,28 +129,20 @@ export class Hype<
|
|||
const res = c.res.clone()
|
||||
const html = await res.text()
|
||||
const formatted = formatHTML(html)
|
||||
c.res = new Response(formatted, c.res)
|
||||
const headers = new Headers(c.res.headers)
|
||||
headers.delete('content-length')
|
||||
c.res = new Response(formatted, { status: c.res.status, headers })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// serve frontend js
|
||||
this.use('*', async (c, next) => {
|
||||
await fnStorage.run({ fns: new Map(), counter: 0 }, async () => {
|
||||
await next()
|
||||
registerRoutes() {
|
||||
if (this.routesRegistered) return
|
||||
this.routesRegistered = true
|
||||
|
||||
const contentType = c.res.headers.get('content-type')
|
||||
if (!contentType?.includes('text/html')) return
|
||||
|
||||
const fns = feFunctions()
|
||||
if (!fns.length) return
|
||||
|
||||
const res = c.res.clone()
|
||||
const html = await res.text()
|
||||
const newHtml = html.replace('</body>', `<script>${fns.join('\n')}</script></body>`)
|
||||
|
||||
c.res = new Response(newHtml, c.res)
|
||||
})
|
||||
})
|
||||
// healthcheck
|
||||
if (this.props.ok)
|
||||
this.get('/ok', c => c.text('ok'))
|
||||
|
||||
// css reset
|
||||
this.get('/css/reset.css', async c => new Response(CSS_RESET, { headers: { 'Content-Type': 'text/css' } }))
|
||||
|
|
@ -148,23 +152,25 @@ export class Hype<
|
|||
|
||||
// serve transpiled js
|
||||
this.on('GET', ['/client/:path{.+}', '/shared/:path{.+}'], async c => {
|
||||
let path = './src/' + c.req.path.replace('..', '.')
|
||||
const reqPath = resolve('./src/', c.req.path.slice(1))
|
||||
if (!reqPath.startsWith(resolve('./src/'))) return render404(c)
|
||||
|
||||
// path must end in .js or .ts
|
||||
if (!path.endsWith('.js') && !path.endsWith('.ts')) path += '.ts'
|
||||
// strip known extension to get base path
|
||||
const base = reqPath.replace(/\.(js|jsx|ts|tsx)$/, '')
|
||||
|
||||
const ts = path.replace('.js', '.ts')
|
||||
if (await Bun.file(ts).exists())
|
||||
return new Response(await transpile(ts), { headers: { 'Content-Type': 'text/javascript' } })
|
||||
// try TS extensions first (needs transpilation)
|
||||
for (const ext of ['.ts', '.tsx', '.jsx']) {
|
||||
const file = base + ext
|
||||
if (await Bun.file(file).exists())
|
||||
return new Response(await transpile(file), { headers: { 'Content-Type': 'text/javascript' } })
|
||||
}
|
||||
|
||||
else if (await Bun.file(ts + 'x').exists())
|
||||
return new Response(await transpile(ts + 'x'), { headers: { 'Content-Type': 'text/javascript' } })
|
||||
// try plain .js (serve raw)
|
||||
const jsFile = base + '.js'
|
||||
if (await Bun.file(jsFile).exists())
|
||||
return new Response(Bun.file(jsFile), { headers: { 'Content-Type': 'text/javascript' } })
|
||||
|
||||
else if (await Bun.file(path).exists())
|
||||
return new Response(Bun.file(path), { headers: { 'Content-Type': 'text/javascript' } })
|
||||
|
||||
else
|
||||
return render404(c)
|
||||
return render404(c)
|
||||
})
|
||||
|
||||
// file based routing
|
||||
|
|
@ -172,15 +178,15 @@ export class Hype<
|
|||
const pageName = (c.req.param('page') ?? 'index').replace('.', '')
|
||||
if (pageName.startsWith('_')) return render404(c)
|
||||
|
||||
const path = join(process.env.PWD ?? '.', `./src/pages/${pageName}.tsx`)
|
||||
const path = join(process.cwd(), `./src/pages/${pageName}.tsx`)
|
||||
|
||||
if (!(await Bun.file(path).exists()))
|
||||
return render404(c)
|
||||
|
||||
let Layout = defaultLayout
|
||||
const layoutPath = join(process.env.PWD ?? '.', `./src/pages/_layout.tsx`)
|
||||
const layoutPath = join(process.cwd(), `./src/pages/_layout.tsx`)
|
||||
if (await Bun.file(layoutPath).exists()) {
|
||||
let Layout = pageCache.get(layoutPath)
|
||||
Layout = pageCache.get(layoutPath)
|
||||
if (!Layout) {
|
||||
Layout = (await import(layoutPath + `?t=${Date.now()}`)).default
|
||||
pageCache.set(layoutPath, Layout)
|
||||
|
|
@ -217,6 +223,8 @@ export class Hype<
|
|||
|
||||
// find an available port starting from the given port
|
||||
function findAvailablePort(startPort: number, maxAttempts = 100): number {
|
||||
if (process.env.NO_AUTOPORT) return startPort
|
||||
|
||||
for (let port = startPort; port < startPort + maxAttempts; port++) {
|
||||
try {
|
||||
const server = Bun.serve({ port, fetch: () => new Response() })
|
||||
|
|
@ -231,4 +239,4 @@ function findAvailablePort(startPort: number, maxAttempts = 100): number {
|
|||
|
||||
function render404(c: Context) {
|
||||
return c.text('File not found', 404)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ const minify = function(el) {
|
|||
.replace(/>\s+</g, '><')
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/\s>/g, '>')
|
||||
.replace(/>\s/g, '>')
|
||||
.replace(/\s</g, '<')
|
||||
.replace(/class=["']\s/g, function(match) {
|
||||
return match.replace(/\s/g, '');
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,22 +1,6 @@
|
|||
import { type Context } from 'hono'
|
||||
import { stat } from 'fs/promises'
|
||||
|
||||
// template literal tag for inline CSS. returns a <style> tag
|
||||
export function css(strings: TemplateStringsArray, ...values: any[]) {
|
||||
return <style dangerouslySetInnerHTML={{
|
||||
__html: String.raw({ raw: strings }, ...values)
|
||||
}} />
|
||||
}
|
||||
|
||||
const transpiler = new Bun.Transpiler({ loader: 'tsx' })
|
||||
|
||||
// template literal tag for inline JS. transpiles and returns a <script> tag
|
||||
export function js(strings: TemplateStringsArray, ...values: any[]) {
|
||||
return <script dangerouslySetInnerHTML={{
|
||||
__html: transpiler.transformSync(String.raw({ raw: strings }, ...values))
|
||||
}} />
|
||||
}
|
||||
|
||||
// lighten a hex color by blending with white. opacity 1 = original, 0 = white
|
||||
export function lightenColor(hex: string, opacity: number): string {
|
||||
// Remove # if present
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user