diff options
-rwxr-xr-x | bun.lockb | bin | 2002 -> 3084 bytes | |||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/index.ts | 73 |
3 files changed, 67 insertions, 8 deletions
diff --git a/bun.lockb b/bun.lockb index 5ccd483..2bb84c6 100755 --- a/bun.lockb +++ b/bun.lockb Binary files differdiff --git a/package.json b/package.json index 314e9f9..7798b26 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "homestead", "module": "src/index.ts", "devDependencies": { + "@types/mime-types": "^2.1.1", "bun-html-live-reload": "^0.1.1", "bun-types": "latest" }, @@ -10,6 +11,7 @@ }, "type": "module", "dependencies": { + "mime-types": "^2.1.35", "toml": "^3.0.0" } } diff --git a/src/index.ts b/src/index.ts index b6d0713..1616df7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,8 @@ import path from "node:path"; import fs, { Stats } from "node:fs"; import fsp from "node:fs/promises"; import { withHtmlLiveReload } from "bun-html-live-reload"; +import mime from "mime-types"; +import type { BunFile } from "bun-types"; import readConfig from "./config"; @@ -13,6 +15,8 @@ const defaultHeaders = config.extra.headers; type File = { filename: string; + handle: BunFile; + relPath: string; headers?: Record<string, string>; size: number; mtime: Date; @@ -20,7 +24,12 @@ type File = { let files = new Map<string, File>(); -function registerFile(pathname: string, filename: string, stat: Stats): void { +function registerFile( + path: string, + pathname: string, + filename: string, + stat: Stats, +): void { pathname = "/" + (pathname === "." || pathname === "./" ? "" : pathname); if (files.get(pathname) !== undefined) { @@ -28,6 +37,8 @@ function registerFile(pathname: string, filename: string, stat: Stats): void { } files.set(pathname, { filename, + relPath: "/" + path, + handle: Bun.file(filename), headers: pathname === "/404.html" ? Object.assign({}, defaultHeaders, { "cache-control": "no-cache" }) @@ -46,14 +57,14 @@ function walkDirectory(root: string, dir: string) { if (stat.isDirectory()) { walkDirectory(root, relPath + path.sep); } else if (stat.isFile()) { - if (pathname === "index.html") { - const dir = path.dirname(relPath); - registerFile(dir, absPath, stat); + if (pathname.startsWith("index.html")) { + const dir = relPath.replace("index.html", ""); + registerFile(relPath, dir, absPath, stat); if (dir !== ".") { - registerFile(dir + path.sep, absPath, stat); + registerFile(relPath, dir + path.sep, absPath, stat); } } - registerFile(relPath, absPath, stat); + registerFile(relPath, relPath, absPath, stat); } } } @@ -63,24 +74,41 @@ walkDirectory(publicDir, ""); async function serveFile( file: File | undefined, statusCode: number = 200, + extraHeaders: Record<string, string> = {}, ): Promise<Response> { - if (file && (await fsp.exists(file.filename))) { - return new Response(Bun.file(file.filename), { + if (file && file.handle.exists()) { + return new Response(file.handle, { headers: { "last-modified": file.mtime.toUTCString(), + ...extraHeaders, ...(file.headers || defaultHeaders), }, status: statusCode, }); } else { + // TODO return encoded return serveFile(files.get("/404.html"), 404); } } +async function serveEncodedFile( + file: File | undefined, + statusCode: number = 200, + extraHeaders: Record<string, string> = {}, +): Promise<Response> { + const res = await serveFile(file, statusCode, extraHeaders); + res.headers.delete("content-disposition"); + return res; +} + function parseIfModifiedSinceHeader(header: string | null): number { return header ? new Date(header).getTime() + 999 : 0; } +function getMIME(filename: string): string { + return mime.contentType(path.extname(filename)) || "text/plain"; +} + export default withHtmlLiveReload({ fetch: async function (request) { const pathname = new URL(request.url).pathname; @@ -92,6 +120,35 @@ export default withHtmlLiveReload({ ) { return new Response("", { status: 304 }); } + const encodings = (request.headers.get("accept-encoding") || "") + .split(",") + .map((x) => x.trim().toLowerCase()); + if (encodings.includes("br") && files.has(file.relPath + ".br")) { + console.log( + "Using br encoding for user agent", + request.headers.get("user-agent"), + ); + return serveEncodedFile(files.get(file.relPath + ".br"), 200, { + "content-encoding": "br", + "content-type": getMIME(file.filename), + }); + } else if ( + encodings.includes("zstd") && + files.has(file.relPath + ".zst") + ) { + return serveEncodedFile(files.get(file.relPath + ".zst"), 200, { + "content-encoding": "zstd", + "content-type": getMIME(file.filename), + }); + } else if ( + encodings.includes("gzip") && + files.has(file.relPath + ".gz") + ) { + return serveEncodedFile(files.get(file.relPath + ".gz"), 200, { + "content-encoding": "gzip", + "content-type": getMIME(file.filename), + }); + } } return serveFile(file); }, |