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(<>
+
+ - dogs
+ - cats
+ - iguanas
+ - hamsters
+ - snakes
+ - chickens
+ - ...even goats!
+
+ >)
+}
\ 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
+ {apps().map(app => - {app}
)}
+
+ >)
})
//
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"
}