import { serveFile } from "jsr:@std/http/file-server"; import { type App, toValue } from "vue"; import { type Router } from "vue-router"; import { renderToString } from "vue/server-renderer"; import transpileResponse from "./transpile.ts"; import { DOMParser } from "jsr:@b-fuze/deno-dom"; import { join } from "jsr:@std/path/join"; import { exists } from "jsr:@std/fs"; import { type DJSSRContext } from "@/useDJSSRContext.ts"; import { type DJAPIResult, type DJAPIResultMap } from "@/api.ts"; const utf8Decoder = new TextDecoder("utf-8"); const parser = new DOMParser(); function appHeaderScript(params: { appstate: Record; entryPath: string }) { return ` `; } async function* siteEntries(path: string): AsyncGenerator { for await (const dirEnt of Deno.readDir(path)) { if (dirEnt.isDirectory) { yield* siteEntries(join(path, dirEnt.name)); } else if (dirEnt.name === "index_template.html") { yield path.split("/")[1] ?? ""; } } } const publicFiles = siteEntries("public"); const sites: string[] = []; for await (const path of publicFiles) { sites.push(path); } function getAPIResponse(apiReq: Request): Response { let jsonResponse: DJAPIResult | { error: string } | null = null; let status = 200; const pathname = URL.parse(apiReq.url)?.pathname; if (!pathname) { jsonResponse = { error: "Invalid Route" }; status = 400; } if (!jsonResponse && pathname) { const apiPath = pathname.split("/api")[1]; if (apiPath === "/rp-articles") { jsonResponse = [ { name: "Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen", slug: "caffeine" }, { name: "TSH, Temperatur, Puls, und andere Indikatoren bei einer Schilddrüsenunterfunktion", slug: "hypothyroidism", }, ] satisfies DJAPIResultMap["/rp-articles"]; } } const headers = new Headers(); headers.set("Content-Type", "application/json"); return new Response(JSON.stringify(jsonResponse), { status, headers, }); } Deno.serve({ port: 8080, hostname: "0.0.0.0", onListen({ port, hostname }) { console.log(`Listening on port http://${hostname}:${port}/`); }, }, async (req, _conn) => { let response: Response | null = null; if (req.method === "GET") { const pathname = URL.parse(req.url)?.pathname ?? "/"; if (pathname.startsWith("/api/")) { response = getAPIResponse(req); } // Public/static files if (!response) { let filepath = join(".", "public", pathname); if (filepath.endsWith("/")) { filepath = join(filepath, "index.html"); } if (await exists(filepath, { isFile: true })) { response = await serveFile(req, filepath); } } // NPM Vendor deps if (response === null && pathname.startsWith("/deps")) { response = await serveFile(req, `node_modules/${pathname.split("/deps")[1]}`); } // Transpile Code if ( response === null && pathname.startsWith("/app") && (pathname.endsWith(".ts") || pathname.endsWith(".tsx")) && !pathname.endsWith("server.ts") ) { response = await serveFile(req, "./" + pathname); response = await transpileResponse(response, req.url, pathname); } // SSR if (response === null) { const baseDirectoryName = pathname.split("/")[1] ?? ""; if (sites.includes(baseDirectoryName)) { const appLocation = baseDirectoryName === "" ? "home" : baseDirectoryName; const siteTemplate = join("public", baseDirectoryName, "index_template.html"); const siteEntry = join("app", appLocation, "server.ts"); const clientEntry = join("@", appLocation, "client.ts"); const { app, router } = (await import("./" + siteEntry)).default() as { app: App; router: Router | null; }; app.provide("dom-parse", (innerHTML: string) => { return parser.parseFromString(innerHTML, "text/html").documentElement; }); const ssrContext: DJSSRContext = { registry: {}, head: { title: "" } }; if (router) { await router.replace(pathname.split("/generative-energy")[1]); await router.isReady(); } const rendered = await renderToString(app, ssrContext); const content = utf8Decoder.decode(await Deno.readFile(siteTemplate)) .replace(``, rendered) .replaceAll("%TITLE%", toValue(ssrContext.head?.title) ?? "Site") .replace( ``, appHeaderScript({ appstate: ssrContext.registry, entryPath: clientEntry }), ); response = new Response(content, { headers: { "Content-Type": "text/html" } }); } } } else { response = new Response("Only GET allowed.", { status: 500 }); } return response ?? new Response("Not found.", { status: 404 }); }); Deno.addSignalListener("SIGINT", () => { console.info("Shutting down (received SIGINT)"); Deno.exit(); });