updatge
This commit is contained in:
194
main.ts
194
main.ts
@@ -1,16 +1,85 @@
|
||||
import { serveFile } from "jsr:@std/http/file-server";
|
||||
import { createSSRApp } from "vue";
|
||||
import { type App, toValue } from "vue";
|
||||
import { type Router } from "vue-router";
|
||||
import { renderToString } from "vue/server-renderer";
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import Home from "@/home/App.tsx";
|
||||
import GERoot, { routes as geRoutes } from "@/generative-energy/GERoot.tsx";
|
||||
import transpileResponse from "./transpile.ts";
|
||||
import { DOMParser } from 'jsr:@b-fuze/deno-dom'
|
||||
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<string, unknown>; entryPath: string }) {
|
||||
return `<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"vue": "/deps/vue/dist/vue.esm-browser.prod.js",
|
||||
"vue-router": "/deps/vue-router/dist/vue-router.esm-browser.js",
|
||||
"vue/jsx-runtime": "/deps/vue/jsx-runtime/index.mjs",
|
||||
"@vue/devtools-api": "/deps/@vue/devtools-api/lib/esm/index.js",
|
||||
"@/": "/app/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module">
|
||||
window.appstate = ${JSON.stringify(params.appstate)};
|
||||
import('${params.entryPath}');
|
||||
</script>`;
|
||||
}
|
||||
|
||||
async function* siteEntries(path: string): AsyncGenerator<string> {
|
||||
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",
|
||||
@@ -18,63 +87,78 @@ Deno.serve({
|
||||
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('/static/') ||
|
||||
pathname.startsWith('/generative-energy/static/')) {
|
||||
if (pathname.startsWith('/static/')) {
|
||||
return serveFile(req, `public/home/${ pathname }`);
|
||||
} else {
|
||||
return serveFile(req, `public${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");
|
||||
}
|
||||
} else if (pathname === "/") {
|
||||
const rendered = await renderToString(createSSRApp(Home));
|
||||
const content = utf8Decoder.decode(await Deno.readFile("./public/home/index.html"))
|
||||
.replace(`<!-- SSR OUTLET -->`, rendered)
|
||||
.replace(`<!-- SSR HEAD OUTLET -->`, "");
|
||||
return new Response(content, { headers: { "Content-Type": "text/html" } });
|
||||
} else if (pathname.startsWith('/generative-energy')) {
|
||||
const app = createSSRApp(GERoot);
|
||||
const router = createRouter({
|
||||
routes: geRoutes,
|
||||
history: createMemoryHistory('/generative-energy'),
|
||||
});
|
||||
app.use(router);
|
||||
app.provide('dom-parse', (innerHTML: string) => {
|
||||
return parser.parseFromString(innerHTML, 'text/html').documentElement;
|
||||
});
|
||||
const ssrContext = { registry: {}};
|
||||
await router.replace(pathname.split('/generative-energy')[1]);
|
||||
await router.isReady();
|
||||
const rendered = await renderToString(app, ssrContext);
|
||||
const content = utf8Decoder.decode(await Deno.readFile("./public/generative-energy/index.html"))
|
||||
.replace('%TITLE%', 'Generative Energy')
|
||||
.replace('<!-- SSR HEAD OUTLET -->', `
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"vue": "/deps/vue/dist/vue.esm-browser.prod.js",
|
||||
"vue-router": "/deps/vue-router/dist/vue-router.esm-browser.js",
|
||||
"vue/jsx-runtime": "/deps/vue/jsx-runtime/index.mjs",
|
||||
"@vue/devtools-api": "/deps/@vue/devtools-api/lib/esm/index.js",
|
||||
"@/": "/app/"
|
||||
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(`<!-- SSR OUTLET -->`, rendered)
|
||||
.replaceAll("%TITLE%", toValue(ssrContext.head?.title) ?? "Site")
|
||||
.replace(
|
||||
`<!-- SSR HEAD OUTLET -->`,
|
||||
appHeaderScript({ appstate: ssrContext.registry, entryPath: clientEntry }),
|
||||
);
|
||||
response = new Response(content, { headers: { "Content-Type": "text/html" } });
|
||||
}
|
||||
</script>
|
||||
<script> window.appstate = ${ JSON.stringify(ssrContext.registry) }; </script>
|
||||
`)
|
||||
.replace(`<!-- SSR OUTLET -->`, rendered);
|
||||
return new Response(content, { headers: { "Content-Type": "text/html" } });
|
||||
} else if (pathname.startsWith("/app") && (pathname.endsWith(".ts") || pathname.endsWith(".tsx"))) {
|
||||
const response = await serveFile(req, "./" + pathname);
|
||||
return await transpileResponse(response, req.url, pathname);
|
||||
} else if (pathname.startsWith("/deps")) {
|
||||
return serveFile(req, `node_modules/${pathname.split("/deps")[1]}`);
|
||||
}
|
||||
return new Response("Not found.", { status: 404 });
|
||||
}
|
||||
} else {
|
||||
return new Response("Only GET allowed.", { status: 500 });
|
||||
response = new Response("Only GET allowed.", { status: 500 });
|
||||
}
|
||||
|
||||
return response ?? new Response("Not found.", { status: 404 });
|
||||
});
|
||||
|
||||
Deno.addSignalListener("SIGINT", () => {
|
||||
|
||||
Reference in New Issue
Block a user