diff --git a/nose/app/dir/index.tsx b/nose/app/dir/index.tsx index 8f4cb9d..b3dcf12 100644 --- a/nose/app/dir/index.tsx +++ b/nose/app/dir/index.tsx @@ -1,7 +1,7 @@ import { text } from "./other" import { css } from "@utils" -export default (c: Context) => { +export default () => { return <> {css` body { diff --git a/nose/app/routes.tsx b/nose/app/routes.tsx new file mode 100644 index 0000000..bf6185b --- /dev/null +++ b/nose/app/routes.tsx @@ -0,0 +1,29 @@ +import { routes } from "@utils" + +export default routes({ + "GET /": index, + "GET /pets": pets +}) + +function index() { + return <> +

Hi world!

+

Welcome to my personal web page.

+

If you are looking for information on pets, click here:

+

PETS

+ +} + +function pets(c: Context) { + return c.html(<> + + ) +} \ No newline at end of file diff --git a/package.json b/package.json index d8c569e..8a5e5b8 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "pluto", - "module": "index.ts", + "module": "src/server.tsx", "type": "module", "private": true, "scripts": { - "start": "bun src/server.ts", - "dev": "bun --hot src/server.ts" + "start": "bun src/server.tsx", + "dev": "bun --hot src/server.tsx" }, "alias": { "@utils": "./src/utils.tsx", diff --git a/src/server.ts b/src/server.tsx similarity index 79% rename from src/server.ts rename to src/server.tsx index 2edb094..aa8710c 100644 --- a/src/server.ts +++ b/src/server.tsx @@ -5,7 +5,7 @@ import color from "kleur" import { NOSE_ICON, NOSE_DIR, NOSE_BIN, NOSE_APP } from "./config" import { transpile, isFile } from "./utils" -import { serveApp } from "./webapp" +import { apps, serveApp } from "./webapp" // // Hono setup @@ -43,12 +43,29 @@ app.get("/js/:path{.+}", async c => { // app routes // -app.get("*", async c => { +app.use("*", async (c, next) => { const url = new URL(c.req.url) const domains = url.hostname.split(".") - const subdomain = domains.length > 1 ? domains[0]! : "none" + const subdomain = domains.length > 1 ? domains[0]! : "" - return await serveApp(c, subdomain) + if (subdomain) { + const app = serveApp(c, subdomain) + return await app + } + + return next() +}) + +app.get("/", c => { + const url = new URL(c.req.url) + const domain = url.hostname + const port = url.port + + return c.html(<> +

apps

+ + ) }) // diff --git a/src/utils.tsx b/src/utils.tsx index 84ac7f7..bbb7de2 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -1,5 +1,7 @@ +import { Hono } from "hono" import { statSync } from "node:fs" import { stat } from "node:fs/promises" +import { type Handler, toResponse } from "./webapp" // Is the given `path` a file? export function isFile(path: string): boolean { @@ -26,7 +28,6 @@ export function randomID(): string { return Math.random().toString(36).slice(2, 10) } - const transpiler = new Bun.Transpiler({ loader: 'tsx' }) const transpileCache: Record = {} @@ -65,3 +66,25 @@ export function js(strings: TemplateStringsArray, ...values: any[]) { }, '') }} /> } + +// for defining routes in your NOSE webapp +// example: +// export default routes({ +// "GET /": index, +// "GET /pets": pets +// }) +export function routes(def: Record): Hono { + const app = new Hono + + for (const key in def) { + const parts = key.split(" ") // GET /path + const method = parts[0] || "GET" + const path = parts[1] || "/" + + console.log(method, path, def[key]) + //@ts-ignore + app.on(method, path, async c => toResponse(await def[key](c))) + } + + return app +} \ No newline at end of file diff --git a/src/webapp.ts b/src/webapp.ts index 1be7616..7b0a1dd 100644 --- a/src/webapp.ts +++ b/src/webapp.ts @@ -1,18 +1,32 @@ -import type { Context } from "hono" +import { type Context, Hono } from "hono" import type { Child } from "hono/jsx" +import { renderToString } from "hono/jsx/dom/server" import { join } from "node:path" +import { readdirSync } from "node:fs" import { NOSE_APP } from "./config" import { isFile } from "./utils" -type App = (r: Context) => string | Child | Response +export type Handler = (r: Context) => string | Child | Response | Promise +export type App = Hono | Handler export async function serveApp(c: Context, subdomain: string): Promise { const app = await findApp(subdomain) - if (app) - return await toResponse(app(c)) + if (!app) return c.text(`App Not Found: ${subdomain}`, 404) - return c.text(`App Not Found: ${subdomain}`, 404) + if (app instanceof Hono) + return app.fetch(c.req.raw) + else + return toResponse(app(c)) +} + +export function apps(): string[] { + const apps: string[] = [] + + for (const entry of readdirSync(NOSE_APP)) + apps.push(entry.replace(/\.tsx?/, "")) + + return apps } async function findApp(name: string): Promise { @@ -43,13 +57,13 @@ async function loadApp(path: string): Promise { return mod.default as App } -async function toResponse(source: string | Child | Response): Promise { +export function toResponse(source: string | Child | Response): Response { if (source instanceof Response) return source else if (typeof source === "string") return new Response(source) else - return new Response(await source?.toString(), { + return new Response(renderToString(source), { headers: { "Content-Type": "text/html; charset=utf-8" }