From 37ee14f5472a9bb4688238fb89bd1d9f2658e66d Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 13 Sep 2023 09:24:11 +0200 Subject: Return precompressed files, if they exist --- src/index.ts | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 8 deletions(-) (limited to 'src') 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; size: number; mtime: Date; @@ -20,7 +24,12 @@ type File = { let files = new Map(); -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 = {}, ): Promise { - 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 = {}, +): Promise { + 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); }, -- cgit 1.4.1