diff --git a/package.json b/package.json index 21d42ab..743e9a1 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/index.tsx b/src/index.tsx index ad9a239..7fc1993 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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,46 +26,55 @@ const scriptsGlob = new Bun.Glob(__dirname + "/scripts/*"); const server = Bun.serve({ port: 8080, - fetch: requestLog(async (req): Promise => { - if (req.url.endsWith("ws")) { - if (server.upgrade(req)) return new Response("ok"); - } + fetch: withMiddlewares( + async (req): Promise => { + 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(, [theme], new URL(req.url)); + const match = router.match(req); + if (!match) return staticPublic(req); + 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(, layouts), + [theme], + new URL(req.url), + ); - const systemScripts = await Bun.build({ - entrypoints: [ - ...scriptsGlob.scanSync(), - "./src/components/Counter.state.ts", - ], - minify: true, - plugins: [myPlugin], - }); - const scripts = await Promise.all( - systemScripts.outputs.map( - async (script) => - ``, - ), - ); + const systemScripts = await Bun.build({ + entrypoints: [ + ...scriptsGlob.scanSync(), + "./src/components/Counter.state.ts", + ], + minify: true, + plugins: [myPlugin], + }); + const scripts = await Promise.all( + systemScripts.outputs.map( + async (script) => + ``, + ), + ); - const responseData = fillTemplate(template, { - head: renderResult.head + scripts.join(""), - body: renderResult.html, - }); + const responseData = fillTemplate(template, { + head: renderResult.head + scripts.join(""), + body: renderResult.html, + }); - const gzipResponseData = Bun.gzipSync(responseData); - - return new Response(gzipResponseData, { - headers: { - "content-type": "text/html", - "Content-Encoding": "gzip", - }, - }); - }), + return new Response(responseData, { + headers: { + "content-type": "text/html", + }, + }); + }, + requestLog(), + gzip, + requestLog("gzip"), + ), websocket: { message: () => {}, open: async (ws) => { diff --git a/src/lib/fast-web.ts b/src/lib/fast-web.tsx similarity index 82% rename from src/lib/fast-web.ts rename to src/lib/fast-web.tsx index 5b0c346..65b45bd 100644 --- a/src/lib/fast-web.ts +++ b/src/lib/fast-web.tsx @@ -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(``, 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 = {wrapped}; + } + return wrapped; +}; diff --git a/src/middlewares/gzip.ts b/src/middlewares/gzip.ts new file mode 100644 index 0000000..f94979a --- /dev/null +++ b/src/middlewares/gzip.ts @@ -0,0 +1,12 @@ +export const gzip = (handler: (req: Request) => Promise) => { + 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, + }); + }; +}; diff --git a/src/middlewares/request-log.ts b/src/middlewares/request-log.ts index 57b9163..cb90faa 100644 --- a/src/middlewares/request-log.ts +++ b/src/middlewares/request-log.ts @@ -11,15 +11,16 @@ const formatBytes = (bytes: number) => { return bytes + "B"; }; -export const requestLog = (handler: (req: Request) => Promise) => { - 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`, - ); - return response; +export const requestLog = + (name?: string) => (handler: (req: Request) => Promise) => { + 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}${name ? " " + name : ""} -> ${response.headers.get("Content-Type")} ${formatBytes(bytes.length)} in ${delta}ms`, + ); + return response; + }; }; -}; diff --git a/src/middlewares/with-middlewares.ts b/src/middlewares/with-middlewares.ts new file mode 100644 index 0000000..5a711ab --- /dev/null +++ b/src/middlewares/with-middlewares.ts @@ -0,0 +1,12 @@ +type Handler = (req: Request) => Promise; + +export const withMiddlewares = ( + fetchFn: Handler, + ...handlers: ((handler: Handler) => Handler)[] +) => { + return async (req: Request) => { + return handlers.reduce((acc, c) => { + return c(acc); + }, fetchFn)(req); + }; +}; diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx new file mode 100644 index 0000000..224abcd --- /dev/null +++ b/src/pages/_layout.tsx @@ -0,0 +1,3 @@ +import Layout from "../components/Layout"; + +export default Layout; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 65943b7..e2f535a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -12,19 +12,17 @@ export default async function Index() { offset: page * 20, }); return ( - - <> -
Hello World
- - -
    - {pokemon.results.map((r) => ( -
  • {r.name}
  • - ))} -
- Prev - Next - -
+ <> +
Hello World
+ + +
    + {pokemon.results.map((r) => ( +
  • {r.name}
  • + ))} +
+ Prev + Next + ); }