improved middlewares + added _layout support

This commit is contained in:
MasterGordon 2025-07-14 23:40:26 +02:00
parent b8107c8f04
commit 4cc9669468
8 changed files with 134 additions and 68 deletions

View File

@ -15,10 +15,6 @@
},
"dependencies": {
"lightningcss": "^1.29.1",
"pokedex-promise-v2": "^4.2.1",
"serve-static-bun": "^0.5.3",
"tree-sitter-cli": "^0.25.2",
"tree-sitter-typescript": "^0.23.2",
"web-tree-sitter": "^0.25.2"
"serve-static-bun": "^0.5.3"
}
}

View File

@ -1,13 +1,23 @@
import { myPlugin } from "./myPlugin";
import { fillTemplate, render } from "./lib/fast-web";
import {
fillTemplate,
render,
resolveLayoutPaths,
wrapLayouts,
} from "./lib/fast-web";
import { requestLog } from "./middlewares/request-log";
import template from "./template.html" with { type: "text" };
import theme from "./theme.css" with { type: "text" };
import staticDir from "serve-static-bun";
import path from "path";
import { withMiddlewares } from "./middlewares/with-middlewares";
import { gzip } from "./middlewares/gzip";
const pagesDir = __dirname + "/pages";
const router = new Bun.FileSystemRouter({
style: "nextjs",
dir: __dirname + "/pages",
dir: pagesDir,
});
const staticPublic = staticDir("./public");
@ -16,16 +26,24 @@ const scriptsGlob = new Bun.Glob(__dirname + "/scripts/*");
const server = Bun.serve({
port: 8080,
fetch: requestLog(async (req): Promise<Response> => {
fetch: withMiddlewares(
async (req): Promise<Response> => {
if (req.url.endsWith("ws")) {
if (server.upgrade(req)) return new Response("ok");
}
const match = router.match(req);
if (!match) return staticPublic(req);
const Comp = (await import(__dirname + "/pages/" + match?.pathname))
.default;
const renderResult = await render(<Comp />, [theme], new URL(req.url));
const layouts = await resolveLayoutPaths(
path.join(pagesDir, match.src),
pagesDir,
);
const Comp = (await import(path.join(pagesDir, match.src))).default;
const renderResult = await render(
await wrapLayouts(<Comp />, layouts),
[theme],
new URL(req.url),
);
const systemScripts = await Bun.build({
entrypoints: [
@ -47,15 +65,16 @@ const server = Bun.serve({
body: renderResult.html,
});
const gzipResponseData = Bun.gzipSync(responseData);
return new Response(gzipResponseData, {
return new Response(responseData, {
headers: {
"content-type": "text/html",
"Content-Encoding": "gzip",
},
});
}),
},
requestLog(),
gzip,
requestLog("gzip"),
),
websocket: {
message: () => {},
open: async (ws) => {

View File

@ -2,6 +2,7 @@ import { camelToKebab, renderStyle } from "~/system/style-rules";
import { transform } from "lightningcss";
import reset from "./reset.css" with { type: "text" };
import { AsyncLocalStorage } from "async_hooks";
import path from "path";
interface RenderContext {
styles: string[];
@ -122,3 +123,27 @@ export const fillTemplate = (
return acc.replace(`<!-- ${key} -->`, value);
}, template);
};
export const resolveLayoutPaths = async (file: string, rootDir: string) => {
const layouts = [];
let dir = file;
console.log(path.normalize(dir));
console.log(path.normalize(rootDir));
do {
dir = path.join(dir, "..");
const layoutFile = path.join(dir, "_layout.tsx");
if (await Bun.file(layoutFile).exists()) {
layouts.push(layoutFile);
}
} while (path.normalize(dir) != path.normalize(rootDir));
return layouts;
};
export const wrapLayouts = async (element: JSX.Element, layouts: string[]) => {
let wrapped = element;
for (const part of layouts) {
const Layout = (await import(part)).default;
wrapped = <Layout>{wrapped}</Layout>;
}
return wrapped;
};

12
src/middlewares/gzip.ts Normal file
View File

@ -0,0 +1,12 @@
export const gzip = (handler: (req: Request) => Promise<Response>) => {
return async (req: Request) => {
const response = await handler(req);
const bytes = await response.clone().bytes();
const gzipped = Bun.gzipSync(bytes);
return new Response(gzipped, {
headers: { ...response.headers.toJSON(), "Content-Encoding": "gzip" },
status: response.status,
statusText: response.statusText,
});
};
};

View File

@ -11,14 +11,15 @@ const formatBytes = (bytes: number) => {
return bytes + "B";
};
export const requestLog = (handler: (req: Request) => Promise<Response>) => {
export const requestLog =
(name?: string) => (handler: (req: Request) => Promise<Response>) => {
return async (req: Request) => {
const start = performance.now();
const response = await handler(req);
const bytes = await response.clone().bytes();
const delta = performance.now() - start;
console.log(
`[${req.method}] ${req.url} -> ${response.headers.get("Content-Type")} ${formatBytes(bytes.length)} in ${delta}ms`,
`[${req.method}] ${req.url}${name ? " " + name : ""} -> ${response.headers.get("Content-Type")} ${formatBytes(bytes.length)} in ${delta}ms`,
);
return response;
};

View File

@ -0,0 +1,12 @@
type Handler = (req: Request) => Promise<Response>;
export const withMiddlewares = (
fetchFn: Handler,
...handlers: ((handler: Handler) => Handler)[]
) => {
return async (req: Request) => {
return handlers.reduce((acc, c) => {
return c(acc);
}, fetchFn)(req);
};
};

3
src/pages/_layout.tsx Normal file
View File

@ -0,0 +1,3 @@
import Layout from "../components/Layout";
export default Layout;

View File

@ -12,7 +12,6 @@ export default async function Index() {
offset: page * 20,
});
return (
<Layout>
<>
<div>Hello World</div>
<Counter />
@ -25,6 +24,5 @@ export default async function Index() {
<Link href={`/?page=${page - 1}`}>Prev</Link>
<Link href={`/?page=${page + 1}`}>Next</Link>
</>
</Layout>
);
}